diff --git a/hook/src/hooks/debug_drawing.rs b/hook/src/hooks/debug_drawing.rs index a4bf969..578226d 100644 --- a/hook/src/hooks/debug_drawing.rs +++ b/hook/src/hooks/debug_drawing.rs @@ -6,7 +6,11 @@ use na::{Matrix, Matrix4, Point3, Vector3}; use nalgebra as na; use crate::hooks::ExecFn; -use crate::ue::{self, FLinearColor, FRotator, FVector, TArray, UObject}; +use crate::ue::{ + self, get_world, FBatchedLine, FBatchedPoint, FLinearColor, FRotator, FVector, TArray, + ULineBatchComponent, UObject, UWorld, +}; +use crate::util::NN; pub fn kismet_hooks() -> &'static [(&'static str, ExecFn)] { &[ @@ -46,106 +50,10 @@ pub fn kismet_hooks() -> &'static [(&'static str, ExecFn)] { "/Script/Engine.KismetSystemLibrary:DrawDebugBox", exec_draw_debug_box as ExecFn, ), - ( - "/Game/_AssemblyStorm/TestMod/DebugStuff.DebugStuff_C:ReceiveTick", - exec_tick as ExecFn, - ), - ( - "/Game/_AssemblyStorm/TestMod/MintDebugStuff/InitCave.InitCave_C:PathTo", - exec_path_to as ExecFn, - ), - ( - "/Game/_AssemblyStorm/TestMod/MintDebugStuff/InitCave.InitCave_C:SpawnPoints", - exec_spawn_points as ExecFn, - ), - ( - "/Game/_mint/BPL_CSG.BPL_CSG_C:Get Procedural Mesh Vertices", - exec_get_mesh_vertices as ExecFn, - ), - ( - "/Game/_mint/BPL_CSG.BPL_CSG_C:Get Procedural Mesh Triangles", - exec_get_mesh_triangles 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, - - padding: [u8; 0xc8], - - game_state: *const AGameStateBase, - // TODO -} -#[repr(C)] -struct AGameStateBase { - object: ue::UObject, - // TODO -} - -#[repr(C)] -struct AFSDGameState { - object: ue::UObject, - - padding: [u8; 0x3f8], - - csg_world: *const nav::ADeepCSGWorld, - // TODO -} - -#[cfg(test)] -mod test { - use super::*; - - const _: [u8; 0x420] = [0; std::mem::offset_of!(AFSDGameState, csg_world)]; - //const _: [u8; 0x128] = [0; std::mem::size_of::()]; -} - -#[repr(C)] -struct ULineBatchComponent { - vftable: *const ULineBatchComponentVTable, - padding: [u8; 0x448], - batched_lines: TArray, - batched_points: TArray, - // 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, - thickness: f32, - remaining_life_time: f32, - depth_priority: u8, -} -#[derive(Debug, Default, Copy, Clone)] -#[repr(C)] -struct FBatchedPoint { - position: FVector, - color: FLinearColor, - point_size: f32, - remaining_life_time: f32, - depth_priority: u8, -} - -unsafe fn get_batcher(world: NonNull, duration: f32) -> NonNull { +pub unsafe fn get_batcher(world: NonNull, duration: f32) -> NonNull { if duration > 0. { element_ptr!(world => .persistent_line_batcher.*) } else { @@ -155,13 +63,33 @@ unsafe fn get_batcher(world: NonNull, duration: f32) -> NonNull, lines: &[FBatchedLine]) { - if let Some((last, lines_)) = lines.split_last() { - let batched_lines: &mut TArray<_> = element_ptr!(batcher => .batched_lines).as_mut(); - batched_lines.extend_from_slice(lines_); +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 = element_ptr!(batcher => .vftable.*.draw_line.*); + let draw_line = batcher.vftable.draw_line; draw_line( batcher, &last.start, @@ -174,13 +102,12 @@ unsafe fn draw_lines(batcher: NonNull, lines: &[FBatchedLin } } -unsafe fn draw_points(batcher: NonNull, lines: &[FBatchedPoint]) { - if let Some((last, lines)) = lines.split_last() { - let batched_points: &mut TArray<_> = element_ptr!(batcher => .batched_points).as_mut(); - batched_points.extend_from_slice(lines); +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 = element_ptr!(batcher => .vftable.*.draw_point.*); + let draw_point = batcher.vftable.draw_point; draw_point( batcher, &last.position, @@ -192,54 +119,6 @@ unsafe fn draw_points(batcher: NonNull, lines: &[FBatchedPo } } -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> { - // TODO implement UEngine::GetWorldFromContextObject - 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 { - // TODO can be done without allocation by creating and comparing FName instead - 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, @@ -255,8 +134,8 @@ unsafe extern "system" fn exec_draw_debug_line( let thickness: f32 = stack.arg(); if let Some(world) = get_world(world_context) { - let batcher = get_batcher(world, duration); - let f = element_ptr!(batcher => .vftable.*.draw_line.*); + let batcher = get_batcher(world, duration).as_mut(); + let f = batcher.vftable.draw_line; f(batcher, &start, &end, &color, 0, thickness, duration); } @@ -279,8 +158,8 @@ unsafe extern "system" fn exec_draw_debug_point( let duration: f32 = stack.arg(); if let Some(world) = get_world(world_context) { - let batcher = get_batcher(world, duration); - let f = element_ptr!(batcher => .vftable.*.draw_point.*); + let batcher = get_batcher(world, duration).as_mut(); + let f = batcher.vftable.draw_point; f(batcher, &position, &color, size, 0, duration); } @@ -308,7 +187,7 @@ unsafe extern "system" fn exec_draw_debug_circle( let draw_axis: bool = stack.arg(); if let Some(world) = get_world(world_context) { - let batcher = get_batcher(world, duration); + let batcher = get_batcher(world, duration).as_mut(); let line_config = FBatchedLine { color, @@ -383,7 +262,7 @@ unsafe extern "system" fn exec_draw_debug_sphere( let thickness: f32 = stack.arg(); if let Some(world) = get_world(world_context) { - let batcher = get_batcher(world, duration); + let batcher = get_batcher(world, duration).as_mut(); let line_config = FBatchedLine { color, @@ -537,8 +416,8 @@ fn add_circle( } } -unsafe fn draw_cone( - batcher: NonNull, +fn draw_cone( + batcher: &mut ULineBatchComponent, origin: FVector, direction: FVector, length: f32, @@ -656,7 +535,7 @@ unsafe extern "system" fn exec_draw_debug_cone( let thickness: f32 = stack.arg(); if let Some(world) = get_world(world_context) { - let batcher = get_batcher(world, duration); + let batcher = get_batcher(world, duration).as_mut(); draw_cone( batcher, origin, @@ -694,7 +573,7 @@ unsafe extern "system" fn exec_draw_debug_cone_in_degrees( let thickness: f32 = stack.arg(); if let Some(world) = get_world(world_context) { - let batcher = get_batcher(world, duration); + let batcher = get_batcher(world, duration).as_mut(); draw_cone( batcher, origin, @@ -731,7 +610,7 @@ unsafe extern "system" fn exec_draw_debug_cylinder( let thickness: f32 = stack.arg(); if let Some(world) = get_world(world_context) { - let batcher = get_batcher(world, duration); + let batcher = get_batcher(world, duration).as_mut(); let line_config = FBatchedLine { color, @@ -814,7 +693,7 @@ unsafe extern "system" fn exec_draw_debug_capsule( let thickness: f32 = stack.arg(); if let Some(world) = get_world(world_context) { - let batcher = get_batcher(world, duration); + let batcher = get_batcher(world, duration).as_mut(); let line_config = FBatchedLine { color, @@ -961,7 +840,7 @@ struct DebugBox { thickness: f32, } impl DebugBox { - unsafe fn draw(&self, lines: &mut Vec) { + fn draw(&self, lines: &mut Vec) { let Self { center, extent, @@ -1055,7 +934,7 @@ unsafe extern "system" fn exec_draw_debug_box( }; if let Some(world) = get_world(world_context) { - let batcher = element_ptr!(world => .line_batcher.*).nn().unwrap(); + let batcher = get_batcher(world, shape.duration).as_mut(); let mut lines = vec![]; shape.draw(&mut lines); draw_lines(batcher, &lines); @@ -1065,1349 +944,3 @@ unsafe extern "system" fn exec_draw_debug_box( 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(); - - if let Some(world) = get_world(context.nn()) { - nav::render(world); - } - - if !stack.code.is_null() { - stack.code = stack.code.add(1); - } -} - -unsafe extern "system" fn exec_path_to( - context: *mut ue::UObject, - stack: *mut ue::kismet::FFrame, - _result: *mut c_void, -) { - let stack = stack.as_mut().unwrap(); - - let dest: FVector = stack.arg(); - let size: nav::DeepPathFinderSize = stack.arg(); - let type_: nav::DeepPathFinderType = stack.arg(); - let pref: nav::DeepPathFinderPreference = stack.arg(); - let follow_player: bool = stack.arg(); - - if let Some(world) = get_world(context.nn()) { - nav::path_to(world, follow_player.then_some(dest), size, type_, pref); - } - - if !stack.code.is_null() { - stack.code = stack.code.add(1); - } -} - -unsafe extern "system" fn exec_spawn_points( - context: *mut ue::UObject, - stack: *mut ue::kismet::FFrame, - _result: *mut c_void, -) { - let stack = stack.as_mut().unwrap(); - - let dest: FVector = stack.arg(); - let radius: f32 = stack.arg(); - let size: nav::DeepPathFinderSize = stack.arg(); - let type_: nav::DeepPathFinderType = stack.arg(); - - if let Some(world) = get_world(context.nn()) { - nav::spawn_points(world, &dest, radius, size, type_); - } - - if !stack.code.is_null() { - stack.code = stack.code.add(1); - } -} - -#[derive(Debug, Clone)] -#[repr(C)] -pub struct UDeepProceduralMeshComponent { - padding: [u8; 0x488], - chunk_id: nav::FChunkId, - triangle_mesh: *const physx::PxTriangleMesh, -} - -mod physx { - use super::*; - - #[repr(C)] - #[rustfmt::skip] - pub struct PxTriangleVTable { - padding: [u8; 0x28], - pub get_nb_vertices: unsafe extern "system" fn(this: NonNull) -> u32, - pub get_vertices: unsafe extern "system" fn(this: NonNull) -> *const FVector, - pub get_vertices_for_modification: unsafe extern "system" fn(this: NonNull) -> *const FVector, - pub refit_bvh: *const (), - pub get_nb_triangles: unsafe extern "system" fn(this: NonNull) -> u32, - pub get_triangles: unsafe extern "system" fn(this: NonNull) -> *const (), - pub get_triangle_mesh_flags: unsafe extern "system" fn(this: NonNull, &mut u8) -> u8, - } - - #[repr(C)] - pub struct PxTriangleMesh { - pub vftable: NonNull, - // TODO rest - } -} - -unsafe extern "system" fn exec_get_mesh_vertices( - _context: *mut ue::UObject, - stack: *mut ue::kismet::FFrame, - _result: *mut c_void, -) { - let stack = stack.as_mut().unwrap(); - - let mesh: Option> = stack.arg(); - let _world_context: Option> = stack.arg(); - - drop(stack.arg::>()); - let ret: &mut TArray = &mut *(stack.most_recent_property_address as *mut _); - *ret = TArray::new(); - - if let Some(mesh) = mesh { - if let Some(triangle_mesh) = element_ptr!(mesh => .triangle_mesh.*).nn() { - let num = element_ptr!(triangle_mesh => .vftable.*.get_nb_vertices.*)(triangle_mesh); - let ptr = element_ptr!(triangle_mesh => .vftable.*.get_vertices.*)(triangle_mesh); - let slice = std::slice::from_raw_parts(ptr, num as usize); - let c = element_ptr!(mesh => .chunk_id.*); - - for vert in slice { - let position = FVector::new( - c.x as f32 * 800. + vert.x, - c.y as f32 * 800. + vert.y, - c.z as f32 * 800. + vert.z, - ); - ret.push(position); - } - } - } - - if !stack.code.is_null() { - stack.code = stack.code.add(1); - } -} - -unsafe extern "system" fn exec_get_mesh_triangles( - _context: *mut ue::UObject, - stack: *mut ue::kismet::FFrame, - _result: *mut c_void, -) { - let stack = stack.as_mut().unwrap(); - - let mesh: Option> = stack.arg(); - let _world_context: Option> = stack.arg(); - - #[derive(Debug, Clone, Copy)] - #[repr(C)] - struct Tri { - a: T, - b: T, - c: T, - } - impl From> for Tri { - fn from(value: Tri) -> Self { - Self { - a: value.a as u32, - b: value.b as u32, - c: value.c as u32, - } - } - } - - drop(stack.arg::>>()); - let ret: &mut TArray> = &mut *(stack.most_recent_property_address as *mut _); - *ret = TArray::new(); - - ret.clear(); - - if let Some(mesh) = mesh { - if let Some(triangle_mesh) = element_ptr!(mesh => .triangle_mesh.*).nn() { - let vtable = element_ptr!(triangle_mesh => .vftable.*); - let num = element_ptr!(vtable => .get_nb_triangles.*)(triangle_mesh); - let ptr = element_ptr!(vtable => .get_triangles.*)(triangle_mesh); - let mut flags = 0; - let _ = element_ptr!(vtable => .get_triangle_mesh_flags.*)(triangle_mesh, &mut flags); - if flags & 2 != 0 { - for tri in std::slice::from_raw_parts(ptr as *const Tri, num as usize) { - ret.push((*tri).into()); - } - } else { - ret.extend_from_slice(std::slice::from_raw_parts( - ptr as *const Tri, - num as usize, - )); - } - } - } - - if !stack.code.is_null() { - stack.code = stack.code.add(1); - } -} - -mod nav { - use self::ue::TArray; - - use super::*; - - pub unsafe 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); - } - - unsafe fn get_path( - pathfinder: NonNull, - start: &FVector, - end: &FVector, - size: DeepPathFinderSize, - type_: DeepPathFinderType, - pref: DeepPathFinderPreference, - ) -> Option> { - let Ok(get_path) = crate::globals() - .resolution - .get_path - .as_ref() - .map(|r| std::mem::transmute::(r.0 as usize)) - else { - return None; - }; - - let mut path = vec![]; - - let mut start = *start; - - loop { - let mut tmp = TArray::default(); - let mut complete = false; - let res = get_path( - pathfinder, - type_, - size, - pref, - &start, - end, - &mut tmp, - &mut complete, - ); - if res != EPathfinderResult::Success { - return None; - } - path.extend_from_slice(tmp.as_slice()); - if complete { - return Some(path); - } - start = *path.last().unwrap(); - } - } - - unsafe fn path_stuff( - lines: &mut Vec, - points: &mut Vec, - dest: Option, - csg_world: NonNull, - size: DeepPathFinderSize, - type_: DeepPathFinderType, - pref: DeepPathFinderPreference, - ) { - //println!("csg_world {csg_world:?}"); - //let nav = element_ptr!(csg_world => .active_nav_data.*.nav_sets5); - //let nodes = element_ptr!(nav => .nodes.*); - //let connections = element_ptr!(nav => .connections); - - if let Some(pathfinder) = element_ptr!(csg_world => .pathfinder.*).nn() { - for i in -5..=5 { - let path = get_path( - pathfinder, - &FVector::new(1000.0, i as f32 * 100.0, 0.), - &dest.unwrap_or_else(|| FVector::new(-1000.0, i as f32 * 100.0, 0.)), - size, - type_, - pref, - ); - //println!("{} {:?}", complete, res); - if let Some(path) = path { - let mut iter = path.as_slice().iter().peekable(); - while let Some(start) = iter.next() { - if let Some(end) = iter.peek() { - lines.push(FBatchedLine { - start: *start, - end: **end, - color: FLinearColor::new(1., 0., 0., 1.), - thickness: 10., - ..Default::default() - }) - } - } - } - } - /* - let mut spawn_points = TArray::default(); - get_all_spawn_points( - pathfinder, - DeepPathFinderType::Walk, - DeepPathFinderSize::Small, - &FVector::default(), - 250.0, - &mut spawn_points, - ); - - println!("len = {}", spawn_points.len()); - for point in spawn_points.as_slice() { - draw_box( - &mut lines, - *point, - FVector::new(30., 30., 30.), - FLinearColor::new(0., 0., 1., 1.), - ); - } - */ - } - - let mut i: u8 = 0; - //for n in 0..(nodes.count as usize) { - // let node = element_ptr!(nodes.start => + (n).*); - // let pos = node.pathfinder_pos.chunk_id_and_offset; - for x in 0..0 { - // TODO - for y in 0..1 { - for z in 0..1 { - let pos = FChunkIDAndOffset { - chunk_id: FChunkId { - x: x / 4, - y: y / 4, - z: z / 4, - }, - offset: FChunkOffset { - x: x % 4, - y: y % 4, - z: z % 4, - }, - }; - - i += 1; - - let rank = 0; //node.rank - let c = if rank == 0 { - FLinearColor::new(0., 1., 0., 0.5) - } else if rank == 1 { - FLinearColor::new(0., 0., 1., 0.5) - } else { - FLinearColor::new(1., 0., 0., 0.5) - }; - - let get_cell_real: FnGetCellReal = std::mem::transmute(0x143dc2ff0 as usize); - let get_cell_server_real: FnGetCellServerReal = - std::mem::transmute(0x143dc30b0 as usize); - - let core = element_ptr!(csg_world => .core_world.*).nn().unwrap(); - let cell = { - let a = get_cell(core, pos); - let b = get_cell_real(core, NonNull::from(&pos)); - assert_eq!(a, b); - a - }; - let cell_server = { - let a = get_cell_server(core, pos); - let b = get_cell_server_real(core, NonNull::from(&pos)); - assert_eq!(a, b); - a - }; - - //let c = if let Some(cell) = cell { - // let cell = element_ptr!(cell => .*); - // //println!( - // // "{:?} {:?}", - // // node.pathfinder_pos.chunk_id_and_offset, cell.bounding_box - // //); - // FLinearColor::new(cell.solidity as f32 / 255., 0., 0., 0.5) - // //FLinearColor::new( - // // cell.tmp_solidity.max(cell.solidity) as f32 / 255., - // // 0., - // // 0., - // // 0.5, - // //) - //} else { - // c - //}; - - let red = FLinearColor::new(1., 0., 0., 0.5); - let blue = FLinearColor::new(0., 0., 1., 0.5); - let black = FLinearColor::new(0., 0., 0., 0.5); - let c = if let Some(cell) = cell_server { - let cell = element_ptr!(cell => .*); - - //let pmv = cell_server.prevent_spawn_material_volumn.volume1; - //if pmv != 0 { - // println!( - // "{:?} {:?}", - // pos.to_world_pos(), - // cell_server.prevent_spawn_material_volumn - // ); - //} - - //println!("{:032b}", cell_server.danger_material_volume); - //if cell_server.danger_material_volume.volume1 != 0 { - // red - //} else if cell_server.prevent_spawn_material_volumn.volume1 != 0 { - // let index = cell_server.prevent_spawn_material_volumn.volume1; - - // let buffer = element_ptr!(core => .array_pools.*).BitVolumeBuffers1; - // let elm = - // element_ptr!(buffer.inner.Pool.start => + (index as usize).*); - // println!("{:?} {:?}", pos.to_world_pos(), elm); - - // blue - //} else { - // black - //} - //println!(); - if cell.prevent_spawn_material_volume.volume1 != 0 { - let index = cell.prevent_spawn_material_volume.volume1; - let buffer = element_ptr!(core => .array_pools.*).BitVolumeBuffers1; - let elm = element_ptr!(buffer.inner.Pool.start => + (index as usize).*); - //println!("{i:02X} SPAWN volume1 {:02X?}", elm.data); - } - if cell.prevent_spawn_material_volume.volume2 != 0 { - let index = cell.prevent_spawn_material_volume.volume2; - let buffer = element_ptr!(core => .array_pools.*).BitVolumeBuffers2; - let elm = element_ptr!(buffer.inner.Pool.start => + (index as usize).*); - //println!("{i:02X} SPAWN volume2 {:02X?}", elm.data); - } - if cell.prevent_spawn_material_volume.volume3 != 0 { - let index = cell.prevent_spawn_material_volume.volume3; - let buffer = element_ptr!(core => .array_pools.*).BitVolumeBuffers3; - let elm = element_ptr!(buffer.inner.Pool.start => + (index as usize).*); - //println!("{i:02X} SPAWN volume3 {:02X?}", elm.data); - let r = 8; - for x in 0..r { - for y in 0..r { - for z in 0..r { - let d = (elm.data[y + r * z] >> x) & 1; - if d == 0 { - continue; - } - - let w = pos.to_world_pos(); - let position = FVector { - x: w.x + (2. * x as f32 + 1.) / r as f32 * 100. - 100., - y: w.y + (2. * y as f32 + 1.) / r as f32 * 100. - 100., - z: w.z + (2. * z as f32 + 1.) / r as f32 * 100. - 100., - }; - points.push(FBatchedPoint { - position, - color: red, - point_size: 40., - remaining_life_time: 0., - depth_priority: 0, - }) - //draw_box( - // lines, - // FVector { - // x: w.x + (2. * x as f32 + 1.) / r as f32 * 100. - // - 100., - // y: w.y + (2. * y as f32 + 1.) / r as f32 * 100. - // - 100., - // z: w.z + (2. * z as f32 + 1.) / r as f32 * 100. - // - 100., - // }, - // FVector::new(20., 20., 20.), - // FLinearColor::new(0., d as f32 / 256., 0., 1.), - //); - } - } - } - } - if cell.danger_material_volume.volume1 != 0 { - let index = cell.danger_material_volume.volume1; - let buffer = element_ptr!(core => .array_pools.*).BitVolumeBuffers1; - let elm = element_ptr!(buffer.inner.Pool.start => + (index as usize).*); - //println!("{i:02X} DANGER volume1 {:02X?}", elm.data); - } - if cell.danger_material_volume.volume2 != 0 { - let index = cell.danger_material_volume.volume2; - let buffer = element_ptr!(core => .array_pools.*).BitVolumeBuffers2; - let elm = element_ptr!(buffer.inner.Pool.start => + (index as usize).*); - //println!("{i:02X} DANGER volume2 {:02X?}", elm.data); - } - if cell.danger_material_volume.volume3 != 0 { - let index = cell.danger_material_volume.volume3; - let buffer = element_ptr!(core => .array_pools.*).BitVolumeBuffers3; - let elm = element_ptr!(buffer.inner.Pool.start => + (index as usize).*); - //println!("{i:02X} DANGER volume3 {:02X?}", elm.data); - } - black - /* - if cell.sdf_volume.volume3 != 0 { - let index = cell.sdf_volume.volume3; - - let buffer = element_ptr!(core => .array_pools.*).VolumeBuffers3; - let elm = - element_ptr!(buffer.inner.Pool.start => + (index as usize).*); - - //println!("{:?} {:08b?}", pos.to_world_pos(), elm); - //println!("{:?}", elm.data); - - let r = 8; - for x in 0..r { - for y in 0..r { - for z in 0..r { - let d = elm.data[x + r * (y + r * z)]; - if d == 0 { - continue; - } - - let w = pos.to_world_pos(); - draw_box( - lines, - FVector { - x: w.x + (2. * x as f32 + 1.) / r as f32 * 100. - - 100., - y: w.y + (2. * y as f32 + 1.) / r as f32 * 100. - - 100., - z: w.z + (2. * z as f32 + 1.) / r as f32 * 100. - - 100., - }, - FVector::new(20., 20., 20.), - FLinearColor::new(0., d as f32 / 256., 0., 1.), - ); - } - } - } - - red - } else { - black - } - */ - } else { - black - }; - - draw_box( - lines, - pos.to_world_pos(), - FVector { - x: 180., - y: 180., - z: 180., - }, - c, - ); - - //println!("{:?}", elm); - //nodes.start[] - } - } - } - - //let vtable = element_ptr!(csg_world => .object.uobject_base_utility.uobject_base.vtable.*); - //println!("tick {csg_world:?} {}", nodes.count); - } - - unsafe fn nav_stuff( - lines: &mut Vec, - points: &mut Vec, - csg_world: NonNull, - ) { - let nav = element_ptr!(csg_world => .active_nav_data.*.nav_sets5); - let nodes = element_ptr!(nav => .nodes.*); - let connections = element_ptr!(nav => .connections); - for n in 0..(nodes.count as usize) { - let node = element_ptr!(nodes.start => + (n).*); - let w = node.pathfinder_pos.chunk_id_and_offset.to_world_pos(); - let s = node.pathfinder_pos; - let r = 8; - draw_box( - lines, - FVector { - x: w.x + (2. * s.sub_x as f32 + 1.) / r as f32 * 100. - 100., - y: w.y + (2. * s.sub_y as f32 + 1.) / r as f32 * 100. - 100., - z: w.z + (2. * s.sub_z as f32 + 1.) / r as f32 * 100. - 100., - }, - FVector::new(10., 10., 10.), - FLinearColor::new(0., 1., 0., 1.), - ) - } - } - - pub unsafe fn path_to( - world: NonNull, - dest: Option, - size: DeepPathFinderSize, - type_: DeepPathFinderType, - pref: DeepPathFinderPreference, - ) { - let batcher = element_ptr!(world => .line_batcher.*).nn().unwrap(); - - let mut lines = vec![]; - let mut points = vec![]; - - let game_state = element_ptr!(world => .game_state.* as AFSDGameState); - let csg_world = element_ptr!(game_state => .csg_world.*).nn(); - if let Some(csg_world) = csg_world { - path_stuff(&mut lines, &mut points, dest, csg_world, size, type_, pref); - } - - draw_lines(batcher, &lines); - draw_points(batcher, &points); - } - - pub unsafe fn spawn_points( - world: NonNull, - dest: &FVector, - radius: f32, - size: DeepPathFinderSize, - type_: DeepPathFinderType, - ) { - let Ok(get_all_spawn_points) = crate::globals() - .resolution - .get_all_spawn_points_in_sphere - .as_ref() - .map(|r| std::mem::transmute::(r.0 as usize)) - else { - return; - }; - - let batcher = element_ptr!(world => .line_batcher.*).nn().unwrap(); - - let mut lines = vec![]; - let mut points = vec![]; - - let game_state = element_ptr!(world => .game_state.* as AFSDGameState); - let csg_world = element_ptr!(game_state => .csg_world.*).nn(); - if let Some(csg_world) = csg_world { - if let Some(pathfinder) = element_ptr!(csg_world => .pathfinder.*).nn() { - let mut spawn_points = TArray::default(); - get_all_spawn_points(pathfinder, type_, size, dest, radius, &mut spawn_points); - - for point in spawn_points.as_slice() { - draw_box( - &mut lines, - *point, - FVector::new(30., 30., 30.), - FLinearColor::new(0., 0., 1., 1.), - ); - } - } - } - - draw_lines(batcher, &lines); - draw_points(batcher, &points); - } - - pub unsafe fn render(world: NonNull) { - let batcher = element_ptr!(world => .line_batcher.*).nn().unwrap(); - - let mut lines = vec![]; - let mut points = vec![]; - - //let get_all_spawn_points: FnGetAllSpawnPointsInSphere = std::mem::transmute(0x143dc28a0 as usize); - - let game_state = element_ptr!(world => .game_state.* as AFSDGameState); - let csg_world = element_ptr!(game_state => .csg_world.*).nn(); - if let Some(csg_world) = csg_world { - //println!("csg_world {csg_world:?}"); - - //path_stuff(&mut lines, &mut points, csg_world); - //nav_stuff(&mut lines, &mut points, csg_world); - } - - draw_lines(batcher, &lines); - draw_points(batcher, &points); - } - - #[derive(Debug, Clone)] - #[repr(C)] - pub struct FSDVirtualMemAllocator { - padding: [u8; 0x40], - } - - #[derive(Debug, Clone, Copy)] - #[repr(C)] - pub struct FChunkId { - pub x: i16, - pub y: i16, - pub z: i16, - } - - #[derive(Debug, Clone, Copy)] - #[repr(C)] - pub struct FChunkOffset { - pub x: i16, - pub y: i16, - pub z: i16, - } - - #[derive(Debug, Clone, Copy)] - #[repr(C)] - pub struct FChunkIDAndOffset { - chunk_id: FChunkId, - offset: FChunkOffset, - } - impl FChunkIDAndOffset { - fn to_world_pos(&self) -> FVector { - let c = &self.chunk_id; - let o = &self.offset; - - let x = c.x as f32 * 8. + o.x as f32 * 2.; - let y = c.y as f32 * 8. + o.y as f32 * 2.; - let z = c.z as f32 * 8. + o.z as f32 * 2.; - - FVector { - x: x * 100. + 100., - y: y * 100. + 100., - z: z * 100. + 100., - } - } - } - - #[derive(Debug, Clone)] - #[repr(C)] - pub struct FPathfinderCoordinate { - chunk_id_and_offset: FChunkIDAndOffset, - sub_x: u8, - sub_y: u8, - sub_z: u8, - padding: u8, - } - - #[derive(Debug, Clone)] - #[repr(C)] - pub struct NodeIdx { - id: i32, - } - - #[derive(Debug, Clone)] - #[repr(C)] - pub struct ConnectionIdx { - id: i32, - } - - #[derive(Debug, Clone)] - #[repr(C)] - pub struct FDeepNavSetFNode { - pathfinder_pos: FPathfinderCoordinate, - parent: NodeIdx, - rank: u16, - first_connection: ConnectionIdx, - num_calls: u16, - } - impl FDeepNavSetFNode { - fn to_world_pos(&self) -> FVector { - self.pathfinder_pos.chunk_id_and_offset.to_world_pos() - } - } - - #[derive(Debug, Clone)] - #[repr(C)] - pub struct FDeepNavSetFConnection { - node: NodeIdx, - next_connection: ConnectionIdx, - } - - #[derive(Debug, Clone)] - #[repr(C)] - pub struct DeepVirtExpandingArray { - start: *const T, - count: i32, - allocated: i32, - virtual_mem: FSDVirtualMemAllocator, - } - - #[derive(Debug, Clone)] - #[repr(C)] - pub struct FDeepNavSet { - nodes: DeepVirtExpandingArray, - connections: DeepVirtExpandingArray, - } - - #[derive(Debug, Clone)] - #[repr(C)] - pub struct FDeepNavData { - nav_sets1: FDeepNavSet, - nav_sets2: FDeepNavSet, - nav_sets3: FDeepNavSet, - nav_sets4: FDeepNavSet, - nav_sets5: FDeepNavSet, - nav_sets6: FDeepNavSet, - } - - #[derive(Debug, Clone)] - #[repr(C)] - pub struct ADeepCSGWorld { - padding: [u8; 0x6e0], - core_world: *const FCoreCSGWorld, - terrain_scheduler: u64, - create_debris: bool, - pathfinder: *const DeepPathfinder, - active_nav_data: *const FDeepNavData, - next_nav_data: *const FDeepNavData, - } - - //#[repr(C)] - //pub struct ADeepCSGWorld { - // object: ue::UObject, - //} - - #[derive(Debug)] - #[repr(C)] - pub struct FCoreCSGWorld { - init_net_section: [u8; 0x40], - handle_to_material: [u8; 0x40], - default_scanner_material: [u8; 0x40], - unknown: [u8; 0x40], - array_pools: FDeepArrayPools, - section_infos: DeepSparseArray, - cells: DeepVirtExpandingArray, - cells_connectivity: DeepVirtExpandingArray, - cells_pathfinder: DeepVirtExpandingArray, - edge_cells: [[u8; 0x128]; 0x40], //FDeepCellStored[0x40] - padding: [u8; 0x4a00], - sections_allocated: DeepBitArray, //DeepBitArray<5242880> SectionsAllocated; - alloc_server_cells: bool, - } - #[derive(Debug, Clone)] - #[repr(C)] - pub struct DeepBitArray { - high_bits: [u64; 0x500], - bits: [u64; 0x14000], - } - #[derive(Debug, Clone)] - #[repr(C)] - pub struct DeepSparseArray { - num_alloced: i32, - buffer: *const T, - virtual_mem: [u8; 0x40], // FSDVirtualMemAllocator - } - #[derive(Debug, Clone)] - #[repr(C)] - pub struct FDeepSectionInfo { - connectivity: [u8; 0x18], // FDeepChunkStoredConnectivity - section_actor: *const (), // ADeepCSGSection* - triangle_mesh: *const (), // physx::PxTriangleMesh* - cell_offset: u32, - has_no_triangles: u8, - normal_zmin: u8, - normal_zmax: u8, - } - - #[derive(Debug)] - #[repr(C)] - pub struct FDeepArrayPools { - Planes: DeepArrayPool, - SubMeshes: DeepArrayPool, - SubVolumes: DeepArrayPool, - VertexPositions: DeepArrayPool, - Faces: DeepArrayPool, - PhysTriangles: DeepArrayPool, - Debris: DeepArrayPool, - AttachPoints: DeepArrayPool, - FPNodes: DeepArrayPool, - PFCollision: DeepArrayPool, - ConnectivityPoints: DeepArrayPool, - ConnectivitySidePoints: - DeepArrayPool, - InternalConnectivityUF: - DeepArrayPool, - GlobalConnectivityUF: - DeepArrayPool, - VolumeBuffers1: DeepVolumeBufferPool<0x8>, - BitVolumeBuffers1: DeepBitVolumeBufferPool<0x4>, - VolumeBuffers2: DeepVolumeBufferPool<0x40>, - BitVolumeBuffers2: DeepBitVolumeBufferPool<0x10>, - VolumeBuffers3: DeepVolumeBufferPool<0x200>, - BitVolumeBuffers3: DeepBitVolumeBufferPool<0x40>, - } - - #[cfg(test)] - mod test { - use super::*; - const _: [u8; 0x1438 - 0x10a0] = [0; std::mem::size_of::>()]; - const _: [u8; 0x1ad0 - 0x1438] = [0; std::mem::size_of::>()]; - const _: [u8; 0x130] = - [0; std::mem::size_of::>()]; - const _: [u8; 0xaed78] = [0; std::mem::size_of::()]; - const _: [u8; 0x2f30] = [0; std::mem::size_of::()]; - - const _: [u8; 0x6f8] = [0; std::mem::offset_of!(ADeepCSGWorld, pathfinder)]; - - const _: [u8; 0xc4] = [0; std::mem::offset_of!(FDeepCellStored, mesh_bounding_box)]; - const _: [u8; 0x3038] = [0; std::mem::offset_of!(FCoreCSGWorld, section_infos) + 8]; - const _: [u8; 0xc570] = [0; std::mem::offset_of!(FCoreCSGWorld, sections_allocated)]; - const _: [u8; 0x3080] = [0; std::mem::offset_of!(FCoreCSGWorld, cells)]; - const _: [u8; 0x128] = [0; std::mem::size_of::()]; - const _: [u8; 0x1c0] = [0; std::mem::size_of::()]; - - const _: [u8; 0x450] = [0; std::mem::offset_of!(ULineBatchComponent, batched_lines)]; - const _: [u8; 0x34] = [0; std::mem::size_of::()]; - } - - #[derive(Debug, Clone)] - #[repr(C)] - struct FDeepChunkStoredConnectivity { - /* offset 0x000 */ singleSideChunkConnectivity: [u8; 6], - /* offset 0x006 */ numChunkConnectivityRegions: u8, - /* offset 0x008 */ ConnectivyUF0: FChunkGlobalConnectivityUnionFind, - /* offset 0x014 */ - ConnectivityUFs: DeepArrayPool, - //DeepArrayPool::RangeIdx - } - - #[derive(Debug, Clone)] - #[repr(C)] - struct FEncodedChunkId { - /* offset 0x000 */ id: u32, - } - - #[derive(Debug, Clone)] - #[repr(C)] - struct FChunkGlobalConnectivityId { - /* offset 0x000 */ ChunkId: FEncodedChunkId, - /* offset 0x004 */ ChunkIdx: u8, - } - - #[derive(Debug, Clone)] - #[repr(C)] - struct FChunkGlobalConnectivityUnionFind { - /* offset 0x000 */ Parent: FChunkGlobalConnectivityId, - /* offset 0x008 */ Rank: u16, - } - - #[derive(Debug, Clone)] - #[repr(C)] - struct FChunkInternalConnectivityId { - /* offset 0x000 */ CellId: u8, - /* offset 0x001 */ SolidIdx: u8, - } - - #[derive(Debug, Clone)] - #[repr(C)] - struct FChunkInternalConnectivityUnionFind { - /* offset 0x000 */ Parent: FChunkInternalConnectivityId, - /* offset 0x002 */ Rank: u16, - /* offset 0x004 */ ChunkIndex: u8, - } - - #[derive(Debug, Clone)] - #[repr(C)] - struct FTerrainSideConnectivity { - /* offset 0x000 */ SolidIdx: u8, - /* offset 0x001 */ SideIdx: u8, - } - - #[derive(Debug, Clone)] - #[repr(C)] - struct FTerrainConnectivityPoint { - /* offset 0x000 */ Point: FVector, - /* offset 0x00c */ SolidIdx: u8, - /* offset 0x00d */ SideIdx: u8, - } - - #[derive(Debug, Clone)] - #[repr(C)] - struct FPFCollisionKey { - /* offset 0x000 */ val: u32, - } - - #[derive(Debug, Clone)] - #[repr(C)] - struct PFCellNode { - // union - /* offset 0x000 */ //LocalRoot: Type0x98a6d /* TODO: figure out how to name it */, - /* offset 0x000 */ //NumCells: Type0x98a6f /* TODO: figure out how to name it */, - /* offset 0x000 */ //NumSpawnCells: Type0x98a71 /* TODO: figure out how to name it */, - val: u32, - /* offset 0x004 */ GlobalNode: u32, - } - - #[derive(Debug, Clone)] - #[repr(C)] - struct FAttachCSGPoint { - /* offset 0x000 */ Pos: FVector, - /* offset 0x00c */ Index: i32, - } - - #[derive(Debug, Clone)] - #[repr(C)] - struct FDebrisCSGPoint { - /* offset 0x000 */ Pos: FVector, - /* offset 0x00c */ Index: i32, - /* offset 0x010 */ DebrisComponent: *const (), // UDebrisInstances, - } - - #[derive(Debug, Clone)] - #[repr(C)] - struct FTerrainPhysTriangle { - /* offset 0x000 */ BaseIdx: u32, - /* offset 0x004 */ Offset1: u8, - /* offset 0x005 */ Offset2: u8, - /* offset 0x006 */ Material: u16, - } - - #[derive(Debug, Clone)] - #[repr(C)] - struct FTerrainMeshFace { - /* offset 0x000 */ Normal: [u8; 3], - /* offset 0x003 */ VertCount: u8, - } - - #[derive(Debug, Clone)] - #[repr(C)] - pub struct FWindowsCriticalSection { - padding: [u8; 0x28], - } - - #[derive(Debug, Clone)] - #[repr(C)] - pub struct PFTypes_FSectionBuffer { - data: [u8; N], - } - - #[derive(Debug, Clone)] - #[repr(C)] - pub struct PFTypes_FBitBuffer { - data: [u8; N], - } - - #[derive(Debug)] - #[repr(C)] - pub struct DeepVolumeBufferPool { - inner: DeepArenaPool, 128>, - } - - #[derive(Debug)] - #[repr(C)] - pub struct DeepBitVolumeBufferPool { - inner: DeepArenaPool, 256>, - } - - #[derive(Debug)] - #[repr(C)] - pub struct TArrayInline { - inline_data: [T; N], - secondary_data: *const T, - num: i32, - max: i32, - } - - #[derive(Debug)] - #[repr(C)] - pub struct DeepArenaPool { - Pool: DeepVirtExpandingArray, - FreeIndices: TArrayInline, // TODO inline allocator - RefCounts: TArrayInline, // TODO inline allocator - CriticalSection: FWindowsCriticalSection, - } - - #[derive(Debug, Clone)] - #[repr(C)] - struct DeepArrayPoolRange { - start: u32, - end: u32, - handle: *const C, - } - - #[derive(Debug, Clone)] - #[repr(u32)] - enum GarbState { - NotRunning = 0x0, - Running = 0x1, - } - - #[derive(Debug, Clone)] - #[repr(C)] - pub struct DeepArrayPool { - /* offset 0x000 */ Pool: DeepVirtExpandingArray, - /* offset 0x050 */ - ActiveRangeList: [DeepVirtExpandingArray>; 2], - /* offset 0x0f0 */ ActiveRange: u32, - /* offset 0x0f4 */ GState: GarbState, - /* offset 0x0f8 */ GarbIndex: i32, - /* offset 0x0fc */ GarbLastEnd: u32, - /* offset 0x100 */ GarbageAmount: i32, - /* offset 0x108 */ CriticalSection: FWindowsCriticalSection, - } - - #[derive(Debug, Clone)] - #[repr(C)] - pub struct FDeepCSGPlane { - /* offset 0x000 */ plane: FPackedPlane, - /* offset 0x008 */ top: FDeepCSGNode, - /* offset 0x00c */ bottom: FDeepCSGNode, - } - - #[derive(Debug, Clone)] - #[repr(C)] - struct FPackedPlane { - /* offset 0x000 */ x: i16, - /* offset 0x002 */ y: i16, - /* offset 0x004 */ z: i16, - /* offset 0x006 */ w: i16, - } - - #[derive(Debug, Clone)] - #[repr(C)] - struct FSubMeshInfo { - /* offset 0x000 */ StartVertex: i32, - /* offset 0x004 */ StartFace: i32, - /* offset 0x008 */ NumVertices: i32, - /* offset 0x00c */ NumIndices: i32, - /* offset 0x010 */ NumFaces: i32, - /* offset 0x014 */ MaterialType: u32, - /* offset 0x018 */ NormalZMin: u8, - /* offset 0x019 */ NormalZMax: u8, - } - - #[derive(Debug, Clone)] - #[repr(C)] - struct FSubVolumeInfo { - /* offset 0x000 */ MaterialType: u32, - /* offset 0x004 */ Volume: f32, - /* offset 0x008 */ ConnectivityIdx: u8, - /* offset 0x00c */ Center: FVector, - } - - #[derive(Debug, Clone)] - #[repr(C)] - struct FDeepCSGNode { - /* offset 0x000 */ val: u32, - } - - #[derive(Debug, Clone)] - #[repr(C)] - pub struct FTerrainMeshBox { - min: FTerrainMeshVertex, - max: FTerrainMeshVertex, - } - - #[derive(Debug, Clone)] - #[repr(C)] - struct FTerrainMeshVertex { - x: u16, - y: u16, - z: u16, - } - - #[derive(Debug, Clone)] - #[repr(C)] - pub struct FDeepCellStored { - padding_1: [u8; 0x8], - sdf_volume: FPFByteData, - padding_1a: [u8; 0x4], - solidity: u8, - tmp_solidity: u8, - padding_2: [u8; 0xaa], - mesh_bounding_box: FTerrainMeshBox, - padding_3: [u8; 0x28], - - pf_solid_volume_from_objects: FPFBitData, - pf_danger_volume_from_objects: FPFBitData, - pf_block_volume_from_objects: FPFBitData, - - //padding_4: [u8; 0x34], - padding_4: [u8; 0xc], - } - #[derive(Debug, Clone)] - #[repr(C)] - pub struct FDeepCellStoredServer { - danger_material_volume: FPFBitData, - prevent_spawn_material_volume: FPFBitData, - tmp_danger_material_volume: FPFBitData, - tmp_prevent_spawn_material_volume: FPFBitData, - padding_1: [u8; 0x190], - } - - #[derive(Debug, Clone)] - #[repr(C)] - pub struct FPFByteData { - volume1: u32, - volume2: u32, - volume3: u32, - } - - #[derive(Debug, Clone)] - #[repr(C)] - pub struct FPFBitData { - volume1: u32, - volume2: u32, - volume3: u32, - } - - #[derive(Debug, Clone)] - #[repr(C)] - pub struct FDeepCellStoredConnectivity {} - - type FnGetCellReal = unsafe extern "system" fn( - this: NonNull, - chunkAndOffset: NonNull, - ) -> Option>; - type FnGetCellServerReal = unsafe extern "system" fn( - this: NonNull, - chunkAndOffset: NonNull, - ) - -> Option>; - - #[derive(Debug, Default, Clone, Copy)] - #[repr(u8)] - pub enum DeepPathFinderType { - #[default] - Walk = 0x0, - Fly = 0x1, - MAX = 0x2, - } - - #[derive(Debug, Default, Clone, Copy)] - #[repr(u8)] - pub enum DeepPathFinderSize { - Invalid = 0x0, - #[default] - Small = 0x3, - Medium = 0x2, - Large = 0x1, - } - #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] - #[repr(u8)] - enum EPathfinderResult { - Success = 0x0, - Failed_StartingPointNotFound = 0x1, - Failed_EndPointNotFound = 0x2, - Failed_PointsNotConnected = 0x3, - Failed_UsedTooManyNodes = 0x4, - Failed_NotReady = 0x5, - Failed_UnknownError = 0x6, - } - - #[derive(Debug, Default, Clone, Copy)] - #[repr(u8)] - pub enum DeepPathFinderPreference { - #[default] - None = 0x0, - Floor = 0x1, - Walls = 0x2, - Ceiling = 0x3, - } - - #[derive(Debug)] - #[repr(C)] - pub struct DeepPathfinder { - padding: [u8; 0x30], - } - - type FnGetAllSpawnPointsInSphere = unsafe extern "system" fn( - this: NonNull, - path_method: DeepPathFinderType, - path_size: DeepPathFinderSize, - origin: &FVector, - distance: f32, - out: &mut TArray, - ); - - type FnGetPath = unsafe extern "system" fn( - this: NonNull, - path_method: DeepPathFinderType, - path_size: DeepPathFinderSize, - pathPreference: DeepPathFinderPreference, - start: &FVector, - end: &FVector, - path: &mut TArray, - completePath: &mut bool, - ) -> EPathfinderResult; - - unsafe fn get_cell( - this: NonNull, - chunk_and_offset: FChunkIDAndOffset, - ) -> Option> { - get_cell_index(this, chunk_and_offset).map(|index| { - let this = element_ptr!(this => .*); - element_ptr!(this.cells.start => + (index)).nn().unwrap() - }) - } - - unsafe fn get_cell_server( - this: NonNull, - chunk_and_offset: FChunkIDAndOffset, - ) -> Option> { - get_cell_index(this, chunk_and_offset).map(|index| { - element_ptr!((*this.as_ptr()).cells_pathfinder.start => + (index)) - .nn() - .unwrap() - }) - } - - unsafe fn get_cell_index( - this: NonNull, - chunk_and_offset: FChunkIDAndOffset, - ) -> Option { - let this = element_ptr!(this => .*); - - let section_index = (((chunk_and_offset.chunk_id.z + 0x40) as usize) << 0x10) - | (((chunk_and_offset.chunk_id.y + 0x80) as usize) << 0x8) - | ((chunk_and_offset.chunk_id.x + 0x80) as usize); - if section_index > 0x4fffff { - return None; - } - /* - for (i, b) in self.SectionsAllocated.bits.iter().enumerate() { - if *b != 0 { - println!("NON ZERO {i:5x} {b:32x}"); - } - } - */ - let high_index = section_index >> 6; - //dbg!(chunk_and_offset); - //println!( - // "0x{:08x} 0x{:08x} {:x} {:x} {} {:b} {:b} {:x}", - // section_index, - // high_index >> 6, - // high_index & 0x3f, - // std::mem::offset_of!(FCoreCSGWorld, SectionsAllocated), - // section_index & 0x3f, - // 1 << (high_index & 0x3f), - // 1 << (section_index & 0x3f), - // self.SectionsAllocated.high_bits[high_index >> 6], - //); - if 0 == this.sections_allocated.high_bits[high_index >> 6] & (1 << (high_index & 0x3f)) { - return None; - } - if 0 == this.sections_allocated.bits[section_index >> 6] & (1 << (section_index & 0x3f)) { - return None; - } - //println!( - // "0x{:08x} {:10b} {:10b}", - // section_index >> 6, - // self.SectionsAllocated.high_bits[high_index >> 6], - // self.SectionsAllocated.bits[section_index >> 6] - //); - let cell_offset = element_ptr!(this.section_infos.buffer => + (section_index as usize).*) - .cell_offset as usize; - - let index = cell_offset - + (((chunk_and_offset.offset.z as usize * 4) + chunk_and_offset.offset.y as usize) * 4) - + chunk_and_offset.offset.x as usize; - Some(index) - } -} diff --git a/hook/src/hooks/mod.rs b/hook/src/hooks/mod.rs index a09318e..8052159 100644 --- a/hook/src/hooks/mod.rs +++ b/hook/src/hooks/mod.rs @@ -1,6 +1,7 @@ #![allow(clippy::missing_transmute_annotations)] mod debug_drawing; +mod nav; mod server_list; use std::{ @@ -51,6 +52,7 @@ pub unsafe fn initialize() -> Result<()> { .iter() .chain(server_list::kismet_hooks().iter()) .chain(debug_drawing::kismet_hooks().iter()) + .chain(nav::kismet_hooks().iter()) .cloned() .collect::>(); diff --git a/hook/src/hooks/nav.rs b/hook/src/hooks/nav.rs new file mode 100644 index 0000000..e79d89f --- /dev/null +++ b/hook/src/hooks/nav.rs @@ -0,0 +1,1372 @@ +use element_ptr::element_ptr; +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 super::debug_drawing::draw_box; +use super::debug_drawing::draw_lines; +use super::debug_drawing::draw_points; +use super::debug_drawing::get_batcher; +use super::ue; +use super::ExecFn; + +pub fn kismet_hooks() -> &'static [(&'static str, ExecFn)] { + &[ + ( + "/Game/_AssemblyStorm/TestMod/DebugStuff.DebugStuff_C:ReceiveTick", + exec_tick as ExecFn, + ), + ( + "/Game/_AssemblyStorm/TestMod/MintDebugStuff/InitCave.InitCave_C:PathTo", + exec_path_to as ExecFn, + ), + ( + "/Game/_AssemblyStorm/TestMod/MintDebugStuff/InitCave.InitCave_C:SpawnPoints", + exec_spawn_points as ExecFn, + ), + ( + "/Game/_mint/BPL_CSG.BPL_CSG_C:Get Procedural Mesh Vertices", + exec_get_mesh_vertices as ExecFn, + ), + ( + "/Game/_mint/BPL_CSG.BPL_CSG_C:Get Procedural Mesh Triangles", + exec_get_mesh_triangles as ExecFn, + ), + ] +} + +#[repr(C)] +struct AFSDGameState { + object: ue::UObject, + + padding: [u8; 0x3f8], + + csg_world: *const ADeepCSGWorld, + // TODO +} + +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(); + + if let Some(world) = get_world(context.nn()) { + render(world); + } + + if !stack.code.is_null() { + stack.code = stack.code.add(1); + } +} + +unsafe extern "system" fn exec_path_to( + context: *mut ue::UObject, + stack: *mut ue::kismet::FFrame, + _result: *mut c_void, +) { + let stack = stack.as_mut().unwrap(); + + let dest: FVector = stack.arg(); + let size: DeepPathFinderSize = stack.arg(); + let type_: DeepPathFinderType = stack.arg(); + let pref: DeepPathFinderPreference = stack.arg(); + let follow_player: bool = stack.arg(); + + if let Some(world) = get_world(context.nn()) { + path_to(world, follow_player.then_some(dest), size, type_, pref); + } + + if !stack.code.is_null() { + stack.code = stack.code.add(1); + } +} + +unsafe extern "system" fn exec_spawn_points( + context: *mut ue::UObject, + stack: *mut ue::kismet::FFrame, + _result: *mut c_void, +) { + let stack = stack.as_mut().unwrap(); + + let dest: FVector = stack.arg(); + let radius: f32 = stack.arg(); + let size: DeepPathFinderSize = stack.arg(); + let type_: DeepPathFinderType = stack.arg(); + + if let Some(world) = get_world(context.nn()) { + spawn_points(world, &dest, radius, size, type_); + } + + if !stack.code.is_null() { + stack.code = stack.code.add(1); + } +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct UDeepProceduralMeshComponent { + padding: [u8; 0x488], + chunk_id: FChunkId, + triangle_mesh: *const physx::PxTriangleMesh, +} + +mod physx { + use super::*; + + #[repr(C)] + #[rustfmt::skip] + pub struct PxTriangleVTable { + padding: [u8; 0x28], + pub get_nb_vertices: unsafe extern "system" fn(this: NonNull) -> u32, + pub get_vertices: unsafe extern "system" fn(this: NonNull) -> *const FVector, + pub get_vertices_for_modification: unsafe extern "system" fn(this: NonNull) -> *const FVector, + pub refit_bvh: *const (), + pub get_nb_triangles: unsafe extern "system" fn(this: NonNull) -> u32, + pub get_triangles: unsafe extern "system" fn(this: NonNull) -> *const (), + pub get_triangle_mesh_flags: unsafe extern "system" fn(this: NonNull, &mut u8) -> u8, + } + + #[repr(C)] + pub struct PxTriangleMesh { + pub vftable: NonNull, + // TODO rest + } +} + +unsafe extern "system" fn exec_get_mesh_vertices( + _context: *mut ue::UObject, + stack: *mut ue::kismet::FFrame, + _result: *mut c_void, +) { + let stack = stack.as_mut().unwrap(); + + let mesh: Option> = stack.arg(); + let _world_context: Option> = stack.arg(); + + drop(stack.arg::>()); + let ret: &mut TArray = &mut *(stack.most_recent_property_address as *mut _); + *ret = TArray::new(); + + if let Some(mesh) = mesh { + if let Some(triangle_mesh) = element_ptr!(mesh => .triangle_mesh.*).nn() { + let num = element_ptr!(triangle_mesh => .vftable.*.get_nb_vertices.*)(triangle_mesh); + let ptr = element_ptr!(triangle_mesh => .vftable.*.get_vertices.*)(triangle_mesh); + let slice = std::slice::from_raw_parts(ptr, num as usize); + let c = element_ptr!(mesh => .chunk_id.*); + + for vert in slice { + let position = FVector::new( + c.x as f32 * 800. + vert.x, + c.y as f32 * 800. + vert.y, + c.z as f32 * 800. + vert.z, + ); + ret.push(position); + } + } + } + + if !stack.code.is_null() { + stack.code = stack.code.add(1); + } +} + +unsafe extern "system" fn exec_get_mesh_triangles( + _context: *mut ue::UObject, + stack: *mut ue::kismet::FFrame, + _result: *mut c_void, +) { + let stack = stack.as_mut().unwrap(); + + let mesh: Option> = stack.arg(); + let _world_context: Option> = stack.arg(); + + #[derive(Debug, Clone, Copy)] + #[repr(C)] + struct Tri { + a: T, + b: T, + c: T, + } + impl From> for Tri { + fn from(value: Tri) -> Self { + Self { + a: value.a as u32, + b: value.b as u32, + c: value.c as u32, + } + } + } + + drop(stack.arg::>>()); + let ret: &mut TArray> = &mut *(stack.most_recent_property_address as *mut _); + *ret = TArray::new(); + + ret.clear(); + + if let Some(mesh) = mesh { + if let Some(triangle_mesh) = element_ptr!(mesh => .triangle_mesh.*).nn() { + let vtable = element_ptr!(triangle_mesh => .vftable.*); + let num = element_ptr!(vtable => .get_nb_triangles.*)(triangle_mesh); + let ptr = element_ptr!(vtable => .get_triangles.*)(triangle_mesh); + let mut flags = 0; + let _ = element_ptr!(vtable => .get_triangle_mesh_flags.*)(triangle_mesh, &mut flags); + if flags & 2 != 0 { + for tri in std::slice::from_raw_parts(ptr as *const Tri, num as usize) { + ret.push((*tri).into()); + } + } else { + ret.extend_from_slice(std::slice::from_raw_parts( + ptr as *const Tri, + num as usize, + )); + } + } + } + + if !stack.code.is_null() { + stack.code = stack.code.add(1); + } +} + +unsafe fn get_path( + pathfinder: NonNull, + start: &FVector, + end: &FVector, + size: DeepPathFinderSize, + type_: DeepPathFinderType, + pref: DeepPathFinderPreference, +) -> Option> { + let Ok(get_path) = crate::globals() + .resolution + .get_path + .as_ref() + .map(|r| std::mem::transmute::(r.0 as usize)) + else { + return None; + }; + + let mut path = vec![]; + + let mut start = *start; + + loop { + let mut tmp = TArray::default(); + let mut complete = false; + let res = get_path( + pathfinder, + type_, + size, + pref, + &start, + end, + &mut tmp, + &mut complete, + ); + if res != EPathfinderResult::Success { + return None; + } + path.extend_from_slice(tmp.as_slice()); + if complete { + return Some(path); + } + start = *path.last().unwrap(); + } +} + +unsafe fn path_stuff( + lines: &mut Vec, + points: &mut Vec, + dest: Option, + csg_world: NonNull, + size: DeepPathFinderSize, + type_: DeepPathFinderType, + pref: DeepPathFinderPreference, +) { + //println!("csg_world {csg_world:?}"); + //let nav = element_ptr!(csg_world => .active_nav_data.*.nav_sets5); + //let nodes = element_ptr!(nav => .nodes.*); + //let connections = element_ptr!(nav => .connections); + + if let Some(pathfinder) = element_ptr!(csg_world => .pathfinder.*).nn() { + for i in -5..=5 { + let path = get_path( + pathfinder, + &FVector::new(1000.0, i as f32 * 100.0, 0.), + &dest.unwrap_or_else(|| FVector::new(-1000.0, i as f32 * 100.0, 0.)), + size, + type_, + pref, + ); + //println!("{} {:?}", complete, res); + if let Some(path) = path { + let mut iter = path.as_slice().iter().peekable(); + while let Some(start) = iter.next() { + if let Some(end) = iter.peek() { + lines.push(FBatchedLine { + start: *start, + end: **end, + color: FLinearColor::new(1., 0., 0., 1.), + thickness: 10., + ..Default::default() + }) + } + } + } + } + /* + let mut spawn_points = TArray::default(); + get_all_spawn_points( + pathfinder, + DeepPathFinderType::Walk, + DeepPathFinderSize::Small, + &FVector::default(), + 250.0, + &mut spawn_points, + ); + + println!("len = {}", spawn_points.len()); + for point in spawn_points.as_slice() { + draw_box( + &mut lines, + *point, + FVector::new(30., 30., 30.), + FLinearColor::new(0., 0., 1., 1.), + ); + } + */ + } + + let mut i: u8 = 0; + //for n in 0..(nodes.count as usize) { + // let node = element_ptr!(nodes.start => + (n).*); + // let pos = node.pathfinder_pos.chunk_id_and_offset; + for x in 0..0 { + // TODO + for y in 0..1 { + for z in 0..1 { + let pos = FChunkIDAndOffset { + chunk_id: FChunkId { + x: x / 4, + y: y / 4, + z: z / 4, + }, + offset: FChunkOffset { + x: x % 4, + y: y % 4, + z: z % 4, + }, + }; + + i += 1; + + let rank = 0; //node.rank + let c = if rank == 0 { + FLinearColor::new(0., 1., 0., 0.5) + } else if rank == 1 { + FLinearColor::new(0., 0., 1., 0.5) + } else { + FLinearColor::new(1., 0., 0., 0.5) + }; + + let get_cell_real: FnGetCellReal = std::mem::transmute(0x143dc2ff0 as usize); + let get_cell_server_real: FnGetCellServerReal = + std::mem::transmute(0x143dc30b0 as usize); + + let core = element_ptr!(csg_world => .core_world.*).nn().unwrap(); + let cell = { + let a = get_cell(core, pos); + let b = get_cell_real(core, NonNull::from(&pos)); + assert_eq!(a, b); + a + }; + let cell_server = { + let a = get_cell_server(core, pos); + let b = get_cell_server_real(core, NonNull::from(&pos)); + assert_eq!(a, b); + a + }; + + //let c = if let Some(cell) = cell { + // let cell = element_ptr!(cell => .*); + // //println!( + // // "{:?} {:?}", + // // node.pathfinder_pos.chunk_id_and_offset, cell.bounding_box + // //); + // FLinearColor::new(cell.solidity as f32 / 255., 0., 0., 0.5) + // //FLinearColor::new( + // // cell.tmp_solidity.max(cell.solidity) as f32 / 255., + // // 0., + // // 0., + // // 0.5, + // //) + //} else { + // c + //}; + + let red = FLinearColor::new(1., 0., 0., 0.5); + let blue = FLinearColor::new(0., 0., 1., 0.5); + let black = FLinearColor::new(0., 0., 0., 0.5); + let c = if let Some(cell) = cell_server { + let cell = element_ptr!(cell => .*); + + //let pmv = cell_server.prevent_spawn_material_volumn.volume1; + //if pmv != 0 { + // println!( + // "{:?} {:?}", + // pos.to_world_pos(), + // cell_server.prevent_spawn_material_volumn + // ); + //} + + //println!("{:032b}", cell_server.danger_material_volume); + //if cell_server.danger_material_volume.volume1 != 0 { + // red + //} else if cell_server.prevent_spawn_material_volumn.volume1 != 0 { + // let index = cell_server.prevent_spawn_material_volumn.volume1; + + // let buffer = element_ptr!(core => .array_pools.*).BitVolumeBuffers1; + // let elm = + // element_ptr!(buffer.inner.Pool.start => + (index as usize).*); + // println!("{:?} {:?}", pos.to_world_pos(), elm); + + // blue + //} else { + // black + //} + //println!(); + if cell.prevent_spawn_material_volume.volume1 != 0 { + let index = cell.prevent_spawn_material_volume.volume1; + let buffer = element_ptr!(core => .array_pools.*).BitVolumeBuffers1; + let elm = element_ptr!(buffer.inner.Pool.start => + (index as usize).*); + //println!("{i:02X} SPAWN volume1 {:02X?}", elm.data); + } + if cell.prevent_spawn_material_volume.volume2 != 0 { + let index = cell.prevent_spawn_material_volume.volume2; + let buffer = element_ptr!(core => .array_pools.*).BitVolumeBuffers2; + let elm = element_ptr!(buffer.inner.Pool.start => + (index as usize).*); + //println!("{i:02X} SPAWN volume2 {:02X?}", elm.data); + } + if cell.prevent_spawn_material_volume.volume3 != 0 { + let index = cell.prevent_spawn_material_volume.volume3; + let buffer = element_ptr!(core => .array_pools.*).BitVolumeBuffers3; + let elm = element_ptr!(buffer.inner.Pool.start => + (index as usize).*); + //println!("{i:02X} SPAWN volume3 {:02X?}", elm.data); + let r = 8; + for x in 0..r { + for y in 0..r { + for z in 0..r { + let d = (elm.data[y + r * z] >> x) & 1; + if d == 0 { + continue; + } + + let w = pos.to_world_pos(); + let position = FVector { + x: w.x + (2. * x as f32 + 1.) / r as f32 * 100. - 100., + y: w.y + (2. * y as f32 + 1.) / r as f32 * 100. - 100., + z: w.z + (2. * z as f32 + 1.) / r as f32 * 100. - 100., + }; + points.push(FBatchedPoint { + position, + color: red, + point_size: 40., + remaining_life_time: 0., + depth_priority: 0, + }) + //draw_box( + // lines, + // FVector { + // x: w.x + (2. * x as f32 + 1.) / r as f32 * 100. + // - 100., + // y: w.y + (2. * y as f32 + 1.) / r as f32 * 100. + // - 100., + // z: w.z + (2. * z as f32 + 1.) / r as f32 * 100. + // - 100., + // }, + // FVector::new(20., 20., 20.), + // FLinearColor::new(0., d as f32 / 256., 0., 1.), + //); + } + } + } + } + if cell.danger_material_volume.volume1 != 0 { + let index = cell.danger_material_volume.volume1; + let buffer = element_ptr!(core => .array_pools.*).BitVolumeBuffers1; + let elm = element_ptr!(buffer.inner.Pool.start => + (index as usize).*); + //println!("{i:02X} DANGER volume1 {:02X?}", elm.data); + } + if cell.danger_material_volume.volume2 != 0 { + let index = cell.danger_material_volume.volume2; + let buffer = element_ptr!(core => .array_pools.*).BitVolumeBuffers2; + let elm = element_ptr!(buffer.inner.Pool.start => + (index as usize).*); + //println!("{i:02X} DANGER volume2 {:02X?}", elm.data); + } + if cell.danger_material_volume.volume3 != 0 { + let index = cell.danger_material_volume.volume3; + let buffer = element_ptr!(core => .array_pools.*).BitVolumeBuffers3; + let elm = element_ptr!(buffer.inner.Pool.start => + (index as usize).*); + //println!("{i:02X} DANGER volume3 {:02X?}", elm.data); + } + black + /* + if cell.sdf_volume.volume3 != 0 { + let index = cell.sdf_volume.volume3; + + let buffer = element_ptr!(core => .array_pools.*).VolumeBuffers3; + let elm = + element_ptr!(buffer.inner.Pool.start => + (index as usize).*); + + //println!("{:?} {:08b?}", pos.to_world_pos(), elm); + //println!("{:?}", elm.data); + + let r = 8; + for x in 0..r { + for y in 0..r { + for z in 0..r { + let d = elm.data[x + r * (y + r * z)]; + if d == 0 { + continue; + } + + let w = pos.to_world_pos(); + draw_box( + lines, + FVector { + x: w.x + (2. * x as f32 + 1.) / r as f32 * 100. + - 100., + y: w.y + (2. * y as f32 + 1.) / r as f32 * 100. + - 100., + z: w.z + (2. * z as f32 + 1.) / r as f32 * 100. + - 100., + }, + FVector::new(20., 20., 20.), + FLinearColor::new(0., d as f32 / 256., 0., 1.), + ); + } + } + } + + red + } else { + black + } + */ + } else { + black + }; + + draw_box( + lines, + pos.to_world_pos(), + FVector { + x: 180., + y: 180., + z: 180., + }, + c, + ); + + //println!("{:?}", elm); + //nodes.start[] + } + } + } + + //let vtable = element_ptr!(csg_world => .object.uobject_base_utility.uobject_base.vtable.*); + //println!("tick {csg_world:?} {}", nodes.count); +} + +unsafe fn nav_stuff( + lines: &mut Vec, + points: &mut Vec, + csg_world: NonNull, +) { + let nav = element_ptr!(csg_world => .active_nav_data.*.nav_sets5); + let nodes = element_ptr!(nav => .nodes.*); + let connections = element_ptr!(nav => .connections); + for n in 0..(nodes.count as usize) { + let node = element_ptr!(nodes.start => + (n).*); + let w = node.pathfinder_pos.chunk_id_and_offset.to_world_pos(); + let s = node.pathfinder_pos; + let r = 8; + draw_box( + lines, + FVector { + x: w.x + (2. * s.sub_x as f32 + 1.) / r as f32 * 100. - 100., + y: w.y + (2. * s.sub_y as f32 + 1.) / r as f32 * 100. - 100., + z: w.z + (2. * s.sub_z as f32 + 1.) / r as f32 * 100. - 100., + }, + FVector::new(10., 10., 10.), + FLinearColor::new(0., 1., 0., 1.), + ) + } +} + +pub unsafe fn path_to( + world: NonNull, + dest: Option, + size: DeepPathFinderSize, + type_: DeepPathFinderType, + pref: DeepPathFinderPreference, +) { + let batcher = get_batcher(world, 0.).as_mut(); + + let mut lines = vec![]; + let mut points = vec![]; + + let game_state = element_ptr!(world => .game_state.* as AFSDGameState); + let csg_world = element_ptr!(game_state => .csg_world.*).nn(); + if let Some(csg_world) = csg_world { + path_stuff(&mut lines, &mut points, dest, csg_world, size, type_, pref); + } + + draw_lines(batcher, &lines); + draw_points(batcher, &points); +} + +pub unsafe fn spawn_points( + world: NonNull, + dest: &FVector, + radius: f32, + size: DeepPathFinderSize, + type_: DeepPathFinderType, +) { + let Ok(get_all_spawn_points) = crate::globals() + .resolution + .get_all_spawn_points_in_sphere + .as_ref() + .map(|r| std::mem::transmute::(r.0 as usize)) + else { + return; + }; + + let batcher = get_batcher(world, 0.).as_mut(); + + let mut lines = vec![]; + let mut points = vec![]; + + let game_state = element_ptr!(world => .game_state.* as AFSDGameState); + let csg_world = element_ptr!(game_state => .csg_world.*).nn(); + if let Some(csg_world) = csg_world { + if let Some(pathfinder) = element_ptr!(csg_world => .pathfinder.*).nn() { + let mut spawn_points = TArray::default(); + get_all_spawn_points(pathfinder, type_, size, dest, radius, &mut spawn_points); + + for point in spawn_points.as_slice() { + draw_box( + &mut lines, + *point, + FVector::new(30., 30., 30.), + FLinearColor::new(0., 0., 1., 1.), + ); + } + } + } + + draw_lines(batcher, &lines); + draw_points(batcher, &points); +} + +pub unsafe fn render(world: NonNull) { + let batcher = get_batcher(world, 0.).as_mut(); + + let mut lines = vec![]; + let mut points = vec![]; + + //let get_all_spawn_points: FnGetAllSpawnPointsInSphere = std::mem::transmute(0x143dc28a0 as usize); + + let game_state = element_ptr!(world => .game_state.* as AFSDGameState); + let csg_world = element_ptr!(game_state => .csg_world.*).nn(); + if let Some(csg_world) = csg_world { + //println!("csg_world {csg_world:?}"); + + //path_stuff(&mut lines, &mut points, csg_world); + //nav_stuff(&mut lines, &mut points, csg_world); + } + + draw_lines(batcher, &lines); + draw_points(batcher, &points); +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct FSDVirtualMemAllocator { + padding: [u8; 0x40], +} + +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct FChunkId { + pub x: i16, + pub y: i16, + pub z: i16, +} + +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct FChunkOffset { + pub x: i16, + pub y: i16, + pub z: i16, +} + +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct FChunkIDAndOffset { + chunk_id: FChunkId, + offset: FChunkOffset, +} +impl FChunkIDAndOffset { + fn to_world_pos(&self) -> FVector { + let c = &self.chunk_id; + let o = &self.offset; + + let x = c.x as f32 * 8. + o.x as f32 * 2.; + let y = c.y as f32 * 8. + o.y as f32 * 2.; + let z = c.z as f32 * 8. + o.z as f32 * 2.; + + FVector { + x: x * 100. + 100., + y: y * 100. + 100., + z: z * 100. + 100., + } + } +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct FPathfinderCoordinate { + chunk_id_and_offset: FChunkIDAndOffset, + sub_x: u8, + sub_y: u8, + sub_z: u8, + padding: u8, +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct NodeIdx { + id: i32, +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct ConnectionIdx { + id: i32, +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct FDeepNavSetFNode { + pathfinder_pos: FPathfinderCoordinate, + parent: NodeIdx, + rank: u16, + first_connection: ConnectionIdx, + num_calls: u16, +} +impl FDeepNavSetFNode { + fn to_world_pos(&self) -> FVector { + self.pathfinder_pos.chunk_id_and_offset.to_world_pos() + } +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct FDeepNavSetFConnection { + node: NodeIdx, + next_connection: ConnectionIdx, +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct DeepVirtExpandingArray { + start: *const T, + count: i32, + allocated: i32, + virtual_mem: FSDVirtualMemAllocator, +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct FDeepNavSet { + nodes: DeepVirtExpandingArray, + connections: DeepVirtExpandingArray, +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct FDeepNavData { + nav_sets1: FDeepNavSet, + nav_sets2: FDeepNavSet, + nav_sets3: FDeepNavSet, + nav_sets4: FDeepNavSet, + nav_sets5: FDeepNavSet, + nav_sets6: FDeepNavSet, +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct ADeepCSGWorld { + padding: [u8; 0x6e0], + core_world: *const FCoreCSGWorld, + terrain_scheduler: u64, + create_debris: bool, + pathfinder: *const DeepPathfinder, + active_nav_data: *const FDeepNavData, + next_nav_data: *const FDeepNavData, +} + +//#[repr(C)] +//pub struct ADeepCSGWorld { +// object: ue::UObject, +//} + +#[derive(Debug)] +#[repr(C)] +pub struct FCoreCSGWorld { + init_net_section: [u8; 0x40], + handle_to_material: [u8; 0x40], + default_scanner_material: [u8; 0x40], + unknown: [u8; 0x40], + array_pools: FDeepArrayPools, + section_infos: DeepSparseArray, + cells: DeepVirtExpandingArray, + cells_connectivity: DeepVirtExpandingArray, + cells_pathfinder: DeepVirtExpandingArray, + edge_cells: [[u8; 0x128]; 0x40], //FDeepCellStored[0x40] + padding: [u8; 0x4a00], + sections_allocated: DeepBitArray, //DeepBitArray<5242880> SectionsAllocated; + alloc_server_cells: bool, +} +#[derive(Debug, Clone)] +#[repr(C)] +pub struct DeepBitArray { + high_bits: [u64; 0x500], + bits: [u64; 0x14000], +} +#[derive(Debug, Clone)] +#[repr(C)] +pub struct DeepSparseArray { + num_alloced: i32, + buffer: *const T, + virtual_mem: [u8; 0x40], // FSDVirtualMemAllocator +} +#[derive(Debug, Clone)] +#[repr(C)] +pub struct FDeepSectionInfo { + connectivity: [u8; 0x18], // FDeepChunkStoredConnectivity + section_actor: *const (), // ADeepCSGSection* + triangle_mesh: *const (), // physx::PxTriangleMesh* + cell_offset: u32, + has_no_triangles: u8, + normal_zmin: u8, + normal_zmax: u8, +} + +#[derive(Debug)] +#[repr(C)] +pub struct FDeepArrayPools { + Planes: DeepArrayPool, + SubMeshes: DeepArrayPool, + SubVolumes: DeepArrayPool, + VertexPositions: DeepArrayPool, + Faces: DeepArrayPool, + PhysTriangles: DeepArrayPool, + Debris: DeepArrayPool, + AttachPoints: DeepArrayPool, + FPNodes: DeepArrayPool, + PFCollision: DeepArrayPool, + ConnectivityPoints: DeepArrayPool, + ConnectivitySidePoints: DeepArrayPool, + InternalConnectivityUF: + DeepArrayPool, + GlobalConnectivityUF: + DeepArrayPool, + VolumeBuffers1: DeepVolumeBufferPool<0x8>, + BitVolumeBuffers1: DeepBitVolumeBufferPool<0x4>, + VolumeBuffers2: DeepVolumeBufferPool<0x40>, + BitVolumeBuffers2: DeepBitVolumeBufferPool<0x10>, + VolumeBuffers3: DeepVolumeBufferPool<0x200>, + BitVolumeBuffers3: DeepBitVolumeBufferPool<0x40>, +} + +#[cfg(test)] +mod test { + use super::*; + const _: [u8; 0x1438 - 0x10a0] = [0; std::mem::size_of::>()]; + const _: [u8; 0x1ad0 - 0x1438] = [0; std::mem::size_of::>()]; + const _: [u8; 0x130] = + [0; std::mem::size_of::>()]; + const _: [u8; 0xaed78] = [0; std::mem::size_of::()]; + const _: [u8; 0x2f30] = [0; std::mem::size_of::()]; + + const _: [u8; 0x6f8] = [0; std::mem::offset_of!(ADeepCSGWorld, pathfinder)]; + + const _: [u8; 0xc4] = [0; std::mem::offset_of!(FDeepCellStored, mesh_bounding_box)]; + const _: [u8; 0x3038] = [0; std::mem::offset_of!(FCoreCSGWorld, section_infos) + 8]; + const _: [u8; 0xc570] = [0; std::mem::offset_of!(FCoreCSGWorld, sections_allocated)]; + const _: [u8; 0x3080] = [0; std::mem::offset_of!(FCoreCSGWorld, cells)]; + const _: [u8; 0x128] = [0; std::mem::size_of::()]; + const _: [u8; 0x1c0] = [0; std::mem::size_of::()]; + + const _: [u8; 0x420] = [0; std::mem::offset_of!(AFSDGameState, csg_world)]; + //const _: [u8; 0x128] = [0; std::mem::size_of::()]; +} + +#[derive(Debug, Clone)] +#[repr(C)] +struct FDeepChunkStoredConnectivity { + /* offset 0x000 */ singleSideChunkConnectivity: [u8; 6], + /* offset 0x006 */ numChunkConnectivityRegions: u8, + /* offset 0x008 */ ConnectivyUF0: FChunkGlobalConnectivityUnionFind, + /* offset 0x014 */ + ConnectivityUFs: DeepArrayPool, + //DeepArrayPool::RangeIdx +} + +#[derive(Debug, Clone)] +#[repr(C)] +struct FEncodedChunkId { + /* offset 0x000 */ id: u32, +} + +#[derive(Debug, Clone)] +#[repr(C)] +struct FChunkGlobalConnectivityId { + /* offset 0x000 */ ChunkId: FEncodedChunkId, + /* offset 0x004 */ ChunkIdx: u8, +} + +#[derive(Debug, Clone)] +#[repr(C)] +struct FChunkGlobalConnectivityUnionFind { + /* offset 0x000 */ Parent: FChunkGlobalConnectivityId, + /* offset 0x008 */ Rank: u16, +} + +#[derive(Debug, Clone)] +#[repr(C)] +struct FChunkInternalConnectivityId { + /* offset 0x000 */ CellId: u8, + /* offset 0x001 */ SolidIdx: u8, +} + +#[derive(Debug, Clone)] +#[repr(C)] +struct FChunkInternalConnectivityUnionFind { + /* offset 0x000 */ Parent: FChunkInternalConnectivityId, + /* offset 0x002 */ Rank: u16, + /* offset 0x004 */ ChunkIndex: u8, +} + +#[derive(Debug, Clone)] +#[repr(C)] +struct FTerrainSideConnectivity { + /* offset 0x000 */ SolidIdx: u8, + /* offset 0x001 */ SideIdx: u8, +} + +#[derive(Debug, Clone)] +#[repr(C)] +struct FTerrainConnectivityPoint { + /* offset 0x000 */ Point: FVector, + /* offset 0x00c */ SolidIdx: u8, + /* offset 0x00d */ SideIdx: u8, +} + +#[derive(Debug, Clone)] +#[repr(C)] +struct FPFCollisionKey { + /* offset 0x000 */ val: u32, +} + +#[derive(Debug, Clone)] +#[repr(C)] +struct PFCellNode { + // union + /* offset 0x000 */ //LocalRoot: Type0x98a6d /* TODO: figure out how to name it */, + /* offset 0x000 */ //NumCells: Type0x98a6f /* TODO: figure out how to name it */, + /* offset 0x000 */ //NumSpawnCells: Type0x98a71 /* TODO: figure out how to name it */, + val: u32, + /* offset 0x004 */ GlobalNode: u32, +} + +#[derive(Debug, Clone)] +#[repr(C)] +struct FAttachCSGPoint { + /* offset 0x000 */ Pos: FVector, + /* offset 0x00c */ Index: i32, +} + +#[derive(Debug, Clone)] +#[repr(C)] +struct FDebrisCSGPoint { + /* offset 0x000 */ Pos: FVector, + /* offset 0x00c */ Index: i32, + /* offset 0x010 */ DebrisComponent: *const (), // UDebrisInstances, +} + +#[derive(Debug, Clone)] +#[repr(C)] +struct FTerrainPhysTriangle { + /* offset 0x000 */ BaseIdx: u32, + /* offset 0x004 */ Offset1: u8, + /* offset 0x005 */ Offset2: u8, + /* offset 0x006 */ Material: u16, +} + +#[derive(Debug, Clone)] +#[repr(C)] +struct FTerrainMeshFace { + /* offset 0x000 */ Normal: [u8; 3], + /* offset 0x003 */ VertCount: u8, +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct FWindowsCriticalSection { + padding: [u8; 0x28], +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct PFTypes_FSectionBuffer { + data: [u8; N], +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct PFTypes_FBitBuffer { + data: [u8; N], +} + +#[derive(Debug)] +#[repr(C)] +pub struct DeepVolumeBufferPool { + inner: DeepArenaPool, 128>, +} + +#[derive(Debug)] +#[repr(C)] +pub struct DeepBitVolumeBufferPool { + inner: DeepArenaPool, 256>, +} + +#[derive(Debug)] +#[repr(C)] +pub struct TArrayInline { + inline_data: [T; N], + secondary_data: *const T, + num: i32, + max: i32, +} + +#[derive(Debug)] +#[repr(C)] +pub struct DeepArenaPool { + Pool: DeepVirtExpandingArray, + FreeIndices: TArrayInline, // TODO inline allocator + RefCounts: TArrayInline, // TODO inline allocator + CriticalSection: FWindowsCriticalSection, +} + +#[derive(Debug, Clone)] +#[repr(C)] +struct DeepArrayPoolRange { + start: u32, + end: u32, + handle: *const C, +} + +#[derive(Debug, Clone)] +#[repr(u32)] +enum GarbState { + NotRunning = 0x0, + Running = 0x1, +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct DeepArrayPool { + /* offset 0x000 */ Pool: DeepVirtExpandingArray, + /* offset 0x050 */ + ActiveRangeList: [DeepVirtExpandingArray>; 2], + /* offset 0x0f0 */ ActiveRange: u32, + /* offset 0x0f4 */ GState: GarbState, + /* offset 0x0f8 */ GarbIndex: i32, + /* offset 0x0fc */ GarbLastEnd: u32, + /* offset 0x100 */ GarbageAmount: i32, + /* offset 0x108 */ CriticalSection: FWindowsCriticalSection, +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct FDeepCSGPlane { + /* offset 0x000 */ plane: FPackedPlane, + /* offset 0x008 */ top: FDeepCSGNode, + /* offset 0x00c */ bottom: FDeepCSGNode, +} + +#[derive(Debug, Clone)] +#[repr(C)] +struct FPackedPlane { + /* offset 0x000 */ x: i16, + /* offset 0x002 */ y: i16, + /* offset 0x004 */ z: i16, + /* offset 0x006 */ w: i16, +} + +#[derive(Debug, Clone)] +#[repr(C)] +struct FSubMeshInfo { + /* offset 0x000 */ StartVertex: i32, + /* offset 0x004 */ StartFace: i32, + /* offset 0x008 */ NumVertices: i32, + /* offset 0x00c */ NumIndices: i32, + /* offset 0x010 */ NumFaces: i32, + /* offset 0x014 */ MaterialType: u32, + /* offset 0x018 */ NormalZMin: u8, + /* offset 0x019 */ NormalZMax: u8, +} + +#[derive(Debug, Clone)] +#[repr(C)] +struct FSubVolumeInfo { + /* offset 0x000 */ MaterialType: u32, + /* offset 0x004 */ Volume: f32, + /* offset 0x008 */ ConnectivityIdx: u8, + /* offset 0x00c */ Center: FVector, +} + +#[derive(Debug, Clone)] +#[repr(C)] +struct FDeepCSGNode { + /* offset 0x000 */ val: u32, +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct FTerrainMeshBox { + min: FTerrainMeshVertex, + max: FTerrainMeshVertex, +} + +#[derive(Debug, Clone)] +#[repr(C)] +struct FTerrainMeshVertex { + x: u16, + y: u16, + z: u16, +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct FDeepCellStored { + padding_1: [u8; 0x8], + sdf_volume: FPFByteData, + padding_1a: [u8; 0x4], + solidity: u8, + tmp_solidity: u8, + padding_2: [u8; 0xaa], + mesh_bounding_box: FTerrainMeshBox, + padding_3: [u8; 0x28], + + pf_solid_volume_from_objects: FPFBitData, + pf_danger_volume_from_objects: FPFBitData, + pf_block_volume_from_objects: FPFBitData, + + //padding_4: [u8; 0x34], + padding_4: [u8; 0xc], +} +#[derive(Debug, Clone)] +#[repr(C)] +pub struct FDeepCellStoredServer { + danger_material_volume: FPFBitData, + prevent_spawn_material_volume: FPFBitData, + tmp_danger_material_volume: FPFBitData, + tmp_prevent_spawn_material_volume: FPFBitData, + padding_1: [u8; 0x190], +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct FPFByteData { + volume1: u32, + volume2: u32, + volume3: u32, +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct FPFBitData { + volume1: u32, + volume2: u32, + volume3: u32, +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct FDeepCellStoredConnectivity {} + +type FnGetCellReal = unsafe extern "system" fn( + this: NonNull, + chunkAndOffset: NonNull, +) -> Option>; +type FnGetCellServerReal = unsafe extern "system" fn( + this: NonNull, + chunkAndOffset: NonNull, +) -> Option>; + +#[derive(Debug, Default, Clone, Copy)] +#[repr(u8)] +pub enum DeepPathFinderType { + #[default] + Walk = 0x0, + Fly = 0x1, + MAX = 0x2, +} + +#[derive(Debug, Default, Clone, Copy)] +#[repr(u8)] +pub enum DeepPathFinderSize { + Invalid = 0x0, + #[default] + Small = 0x3, + Medium = 0x2, + Large = 0x1, +} +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +#[repr(u8)] +enum EPathfinderResult { + Success = 0x0, + Failed_StartingPointNotFound = 0x1, + Failed_EndPointNotFound = 0x2, + Failed_PointsNotConnected = 0x3, + Failed_UsedTooManyNodes = 0x4, + Failed_NotReady = 0x5, + Failed_UnknownError = 0x6, +} + +#[derive(Debug, Default, Clone, Copy)] +#[repr(u8)] +pub enum DeepPathFinderPreference { + #[default] + None = 0x0, + Floor = 0x1, + Walls = 0x2, + Ceiling = 0x3, +} + +#[derive(Debug)] +#[repr(C)] +pub struct DeepPathfinder { + padding: [u8; 0x30], +} + +type FnGetAllSpawnPointsInSphere = unsafe extern "system" fn( + this: NonNull, + path_method: DeepPathFinderType, + path_size: DeepPathFinderSize, + origin: &FVector, + distance: f32, + out: &mut TArray, +); + +type FnGetPath = unsafe extern "system" fn( + this: NonNull, + path_method: DeepPathFinderType, + path_size: DeepPathFinderSize, + pathPreference: DeepPathFinderPreference, + start: &FVector, + end: &FVector, + path: &mut TArray, + completePath: &mut bool, +) -> EPathfinderResult; + +unsafe fn get_cell( + this: NonNull, + chunk_and_offset: FChunkIDAndOffset, +) -> Option> { + get_cell_index(this, chunk_and_offset).map(|index| { + let this = element_ptr!(this => .*); + element_ptr!(this.cells.start => + (index)).nn().unwrap() + }) +} + +unsafe fn get_cell_server( + this: NonNull, + chunk_and_offset: FChunkIDAndOffset, +) -> Option> { + get_cell_index(this, chunk_and_offset).map(|index| { + element_ptr!((*this.as_ptr()).cells_pathfinder.start => + (index)) + .nn() + .unwrap() + }) +} + +unsafe fn get_cell_index( + this: NonNull, + chunk_and_offset: FChunkIDAndOffset, +) -> Option { + let this = element_ptr!(this => .*); + + let section_index = (((chunk_and_offset.chunk_id.z + 0x40) as usize) << 0x10) + | (((chunk_and_offset.chunk_id.y + 0x80) as usize) << 0x8) + | ((chunk_and_offset.chunk_id.x + 0x80) as usize); + if section_index > 0x4fffff { + return None; + } + /* + for (i, b) in self.SectionsAllocated.bits.iter().enumerate() { + if *b != 0 { + println!("NON ZERO {i:5x} {b:32x}"); + } + } + */ + let high_index = section_index >> 6; + //dbg!(chunk_and_offset); + //println!( + // "0x{:08x} 0x{:08x} {:x} {:x} {} {:b} {:b} {:x}", + // section_index, + // high_index >> 6, + // high_index & 0x3f, + // std::mem::offset_of!(FCoreCSGWorld, SectionsAllocated), + // section_index & 0x3f, + // 1 << (high_index & 0x3f), + // 1 << (section_index & 0x3f), + // self.SectionsAllocated.high_bits[high_index >> 6], + //); + if 0 == this.sections_allocated.high_bits[high_index >> 6] & (1 << (high_index & 0x3f)) { + return None; + } + if 0 == this.sections_allocated.bits[section_index >> 6] & (1 << (section_index & 0x3f)) { + return None; + } + //println!( + // "0x{:08x} {:10b} {:10b}", + // section_index >> 6, + // self.SectionsAllocated.high_bits[high_index >> 6], + // self.SectionsAllocated.bits[section_index >> 6] + //); + let cell_offset = element_ptr!(this.section_infos.buffer => + (section_index as usize).*) + .cell_offset as usize; + + let index = cell_offset + + (((chunk_and_offset.offset.z as usize * 4) + chunk_and_offset.offset.y as usize) * 4) + + chunk_and_offset.offset.x as usize; + Some(index) +} diff --git a/hook/src/lib.rs b/hook/src/lib.rs index cd7d6d2..cf287cd 100644 --- a/hook/src/lib.rs +++ b/hook/src/lib.rs @@ -1,5 +1,6 @@ mod hooks; mod ue; +mod util; use std::{io::BufReader, path::Path}; diff --git a/hook/src/ue/mod.rs b/hook/src/ue/mod.rs index 918611d..07c4f2f 100644 --- a/hook/src/ue/mod.rs +++ b/hook/src/ue/mod.rs @@ -5,6 +5,7 @@ mod map; mod name; mod object; mod string; +mod world; pub use array::*; pub use malloc::*; @@ -12,6 +13,7 @@ pub use map::*; pub use name::*; pub use object::*; pub use string::*; +pub use world::*; use std::ffi::c_void; diff --git a/hook/src/ue/world.rs b/hook/src/ue/world.rs new file mode 100644 index 0000000..2adf9aa --- /dev/null +++ b/hook/src/ue/world.rs @@ -0,0 +1,91 @@ +use element_ptr::element_ptr; +use std::{cell::UnsafeCell, ptr::NonNull}; + +use crate::util::{CastOptionNN as _, NN as _}; + +use super::{FLinearColor, FVector, TArray, UObject}; + +#[repr(C)] +pub struct UWorld { + pub object: UObject, + pub network_notify: *const (), + pub persistent_level: *const (), // ULevel + pub net_driver: *const (), // UNetDriver + pub line_batcher: *const ULineBatchComponent, + pub persistent_line_batcher: *const ULineBatchComponent, + pub foreground_line_batcher: *const ULineBatchComponent, + + padding: [u8; 0xc8], + + pub game_state: *const AGameStateBase, + // TODO +} + +#[repr(C)] +pub struct AGameStateBase { + object: UObject, + // TODO +} + +#[repr(C)] +pub struct ULineBatchComponent { + pub vftable: &'static ULineBatchComponentVTable, + pub padding: UnsafeCell<[u8; 0x448]>, + pub batched_lines: TArray, + pub batched_points: TArray, + // lots more +} + +#[repr(C)] +#[rustfmt::skip] +pub struct ULineBatchComponentVTable { + pub padding: [*const (); 0x110], + pub draw_line: extern "system" fn(this: &mut ULineBatchComponent, start: &FVector, end: &FVector, color: &FLinearColor, depth_priority: u8, life_time: f32, thickness: f32), + pub draw_point: extern "system" fn(this: &mut ULineBatchComponent, position: &FVector, color: &FLinearColor, point_size: f32, depth_priority: u8, life_time: f32), +} + +#[derive(Debug, Default, Copy, Clone)] +#[repr(C)] +pub struct FBatchedLine { + pub start: FVector, + pub end: FVector, + pub color: FLinearColor, + pub thickness: f32, + pub remaining_life_time: f32, + pub depth_priority: u8, +} +#[derive(Debug, Default, Copy, Clone)] +#[repr(C)] +pub struct FBatchedPoint { + pub position: FVector, + pub color: FLinearColor, + pub point_size: f32, + pub remaining_life_time: f32, + pub depth_priority: u8, +} + +pub unsafe fn get_world(mut ctx: Option>) -> Option> { + // TODO implement UEngine::GetWorldFromContextObject + 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 { + // TODO can be done without allocation by creating and comparing FName instead + 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() +} diff --git a/hook/src/util.rs b/hook/src/util.rs new file mode 100644 index 0000000..ebeae41 --- /dev/null +++ b/hook/src/util.rs @@ -0,0 +1,26 @@ +use element_ptr::element_ptr; +use std::ptr::NonNull; + +use crate::ue; + +pub 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) + } +} +pub trait CastOptionNN { + fn cast(self) -> Option>; +} +impl CastOptionNN for Option> { + fn cast(self) -> Option> { + self.map(|s| s.cast()) + } +}