1use core::{
4 array,
5 cell::RefCell,
6 iter::{self, FusedIterator},
7 mem::size_of,
8};
9
10use bytemuck::{CheckedBitPattern, Pod, Zeroable};
11
12use crate::{
13 file_format::pe, future::retry, signature::Signature, string::ArrayCString, Address, Error,
14 PointerSize, Process,
15};
16
17const CSTR: usize = 128;
18
19pub struct Module {
25 pointer_size: PointerSize,
26 offsets: &'static Offsets,
28 g_engine: Address,
29 g_world: Address,
30 fname_base: Address,
31}
32
33impl Module {
34 pub fn attach(
37 process: &Process,
38 version: Version,
39 main_module_address: Address,
40 ) -> Option<Self> {
41 let pointer_size = pe::MachineType::read(process, main_module_address)?.pointer_size()?;
42 let offsets = Offsets::new(version, pointer_size)?;
43 let module_size = pe::read_size_of_image(process, main_module_address)? as u64;
44 let module_range = (main_module_address, module_size);
45
46 let g_engine = {
47 const GENGINE: &[(Signature<7>, u8)] = &[
48 (Signature::new("A8 01 75 ?? 48 C7 05"), 7),
49 (Signature::new("A8 01 75 ?? C7 05 ??"), 6),
50 ];
51
52 let addr = GENGINE.iter().find_map(|(sig, offset)| {
53 Some(sig.scan_process_range(process, module_range)? + *offset)
54 })?;
55 addr + 0x8 + process.read::<i32>(addr).ok()?
56 };
57
58 let g_world = {
59 const GWORLD: &[(Signature<22>, u8)] = &[
60 (
61 Signature::new(
62 "80 7C 24 ?? 00 ?? ?? 48 8B 3D ?? ?? ?? ?? 48 ?? ?? ?? ?? ?? ?? ??",
63 ),
64 10,
65 ),
66 (
67 Signature::new(
68 "48 8B 05 ?? ?? ?? ?? 48 3B ?? 48 0F 44 ?? 48 89 05 ?? ?? ?? ?? E8",
69 ),
70 3,
71 ),
72 ];
73
74 let addr = GWORLD.iter().find_map(|(sig, offset)| {
75 Some(sig.scan_process_range(process, module_range)? + *offset)
76 })?;
77 addr + 0x4 + process.read::<i32>(addr).ok()?
78 };
79
80 let fname_base = {
81 const FNAME_POOL: &[(Signature<13>, u8)] = &[
82 (Signature::new("74 09 48 8D 15 ?? ?? ?? ?? EB 16 ?? ??"), 5),
83 (Signature::new("89 5C 24 ?? 89 44 24 ?? 74 ?? 48 8D 15"), 13),
84 (Signature::new("57 0F B7 F8 74 ?? B8 ?? ?? ?? ?? 8B 44"), 7),
85 ];
86
87 let addr = FNAME_POOL.iter().find_map(|(sig, offset)| {
88 Some(sig.scan_process_range(process, module_range)? + *offset)
89 })?;
90 addr + 0x4 + process.read::<i32>(addr).ok()?
91 };
92
93 Some(Self {
94 pointer_size,
95 offsets,
97 g_engine,
98 g_world,
99 fname_base,
100 })
101 }
102
103 pub async fn wait_attach(
106 process: &Process,
107 version: Version,
108 main_module_address: Address,
109 ) -> Self {
110 retry(|| Self::attach(process, version, main_module_address)).await
111 }
112
113 pub const fn g_world(&self) -> Address {
115 self.g_world
116 }
117
118 pub const fn g_engine(&self) -> Address {
120 self.g_engine
121 }
122
123 pub fn get_g_world_uobject(&self, process: &Process) -> Option<UObject> {
125 match process.read_pointer(self.g_world, self.pointer_size) {
126 Ok(Address::NULL) | Err(_) => None,
127 Ok(val) => Some(UObject { object: val }),
128 }
129 }
130
131 pub fn get_g_engine_uobject(&self, process: &Process) -> Option<UObject> {
133 match process.read_pointer(self.g_engine, self.pointer_size) {
134 Ok(Address::NULL) | Err(_) => None,
135 Ok(val) => Some(UObject { object: val }),
136 }
137 }
138
139 pub fn get_fname<const N: usize>(
141 &self,
142 process: &Process,
143 key: FNameKey,
144 ) -> Result<ArrayCString<N>, Error> {
145 let addr = process.read_pointer(
146 self.fname_base + (self.pointer_size as u64).wrapping_mul(key.chunk_offset as u64 + 2),
147 self.pointer_size,
148 )? + (key.name_offset as u64).wrapping_mul(size_of::<u16>() as u64);
149
150 let string_size = process
151 .read::<u16>(addr)?
152 .checked_shr(6)
153 .unwrap_or_default() as usize;
154
155 let mut string = process.read::<ArrayCString<N>>(addr + size_of::<u16>() as u64)?;
156 string.set_len(string_size);
157
158 Ok(string)
159 }
160}
161
162#[derive(Copy, Clone)]
173pub struct UObject {
174 object: Address,
175}
176
177impl From<Address> for UObject {
178 fn from(addr: Address) -> Self {
179 UObject { object: addr }
180 }
181}
182
183impl UObject {
184 pub fn new(address: impl Into<Address>) -> Self {
186 Self {
187 object: address.into(),
188 }
189 }
190
191 pub fn get_address(&self) -> Address {
193 self.object
194 }
195
196 pub fn get_fname<const N: usize>(
198 &self,
199 process: &Process,
200 module: &Module,
201 ) -> Result<ArrayCString<N>, Error> {
202 let key = process.read::<FNameKey>(self.object + module.offsets.uobject_fname)?;
203
204 module.get_fname(process, key)
205 }
206
207 fn get_uclass(&self, process: &Process, module: &Module) -> Result<UClass, Error> {
209 match process.read_pointer(
210 self.object + module.offsets.uobject_class,
211 module.pointer_size,
212 ) {
213 Ok(Address::NULL) | Err(_) => Err(Error {}),
214 Ok(val) => Ok(UClass { class: val }),
215 }
216 }
217
218 pub fn get_field_offset(
221 &self,
222 process: &Process,
223 module: &Module,
224 field_name: &str,
225 ) -> Option<u32> {
226 self.get_uclass(process, module)
227 .ok()?
228 .get_field_offset(process, module, field_name)
229 }
230}
231
232#[derive(Copy, Clone)]
242struct UClass {
243 class: Address,
244}
245
246impl UClass {
247 fn properties<'a>(
248 &'a self,
249 process: &'a Process,
250 module: &'a Module,
251 ) -> impl FusedIterator<Item = UProperty> + 'a {
252 let mut current_property = {
260 let mut val = None;
261 let mut current_class = *self;
262
263 while val.is_none() {
264 match process.read_pointer(
265 current_class.class + module.offsets.uclass_property_link,
266 module.pointer_size,
267 ) {
268 Ok(Address::NULL) => match process.read_pointer(
269 current_class.class + module.offsets.uclass_super_field,
270 module.pointer_size,
271 ) {
272 Ok(Address::NULL) | Err(_) => break,
273 Ok(super_field) => {
274 current_class = UClass { class: super_field };
275 }
276 },
277 Ok(current_property_address) => {
278 val = Some(UProperty {
279 property: current_property_address,
280 });
281 }
282 _ => break,
283 }
284 }
285
286 val
287 };
288
289 iter::from_fn(move || match current_property {
290 Some(prop) => match process.read_pointer(
291 prop.property + module.offsets.uproperty_property_link_next,
292 module.pointer_size,
293 ) {
294 Ok(val) => {
295 current_property = match val {
296 Address::NULL => None,
297 _ => Some(UProperty { property: val }),
298 };
299 Some(prop)
300 }
301 _ => None,
302 },
303 _ => None,
304 })
305 .fuse()
306 }
307
308 fn get_field_offset(
311 &self,
312 process: &Process,
313 module: &Module,
314 field_name: &str,
315 ) -> Option<u32> {
316 self.properties(process, module)
317 .find(|field| {
318 field
319 .get_fname::<CSTR>(process, module)
320 .is_ok_and(|name| name.matches(field_name))
321 })?
322 .get_offset(process, module)
323 }
324}
325
326#[derive(Copy, Clone)]
331struct UProperty {
332 property: Address,
333}
334
335impl UProperty {
336 fn get_fname<const N: usize>(
337 &self,
338 process: &Process,
339 module: &Module,
340 ) -> Result<ArrayCString<N>, Error> {
341 let key = process.read::<FNameKey>(self.property + module.offsets.uproperty_fname)?;
342
343 module.get_fname(process, key)
344 }
345
346 fn get_offset(&self, process: &Process, module: &Module) -> Option<u32> {
347 process
348 .read(self.property + module.offsets.uproperty_offset_internal)
349 .ok()
350 }
351}
352
353#[derive(Pod, Zeroable, Copy, Clone, PartialEq, Eq, Debug)]
355#[repr(C)]
356pub struct FNameKey {
357 name_offset: u16,
358 chunk_offset: u16,
359}
360
361impl FNameKey {
362 pub fn is_null(&self) -> bool {
364 self.chunk_offset == 0 && self.name_offset == 0
365 }
366}
367
368#[derive(Clone)]
370pub struct UnrealPointer<const CAP: usize> {
371 cache: RefCell<UnrealPointerCache<CAP>>,
372 base_address: Address,
373 fields: [&'static str; CAP],
374 depth: usize,
375}
376
377#[derive(Clone, Copy)]
378struct UnrealPointerCache<const CAP: usize> {
379 offsets: [u64; CAP],
380 resolved_offsets: usize,
381}
382
383impl<const CAP: usize> UnrealPointer<CAP> {
384 pub fn new(base_address: Address, fields: &[&'static str]) -> Self {
391 let this_fields: [&str; CAP] = {
392 let mut iter = fields.iter();
393 array::from_fn(|_| iter.next().copied().unwrap_or_default())
394 };
395
396 let cache = RefCell::new(UnrealPointerCache {
397 offsets: [u64::default(); CAP],
398 resolved_offsets: usize::default(),
399 });
400
401 Self {
402 cache,
403 base_address,
404 fields: this_fields,
405 depth: fields.len().min(CAP),
406 }
407 }
408
409 fn find_offsets(&self, process: &Process, module: &Module) -> Result<(), Error> {
411 let mut cache = self.cache.borrow_mut();
412
413 if cache.resolved_offsets == self.depth {
415 return Ok(());
416 }
417
418 let mut current_uobject = UObject {
422 object: match cache.resolved_offsets {
423 0 => process.read_pointer(self.base_address, module.pointer_size)?,
424 x => {
425 let mut addr = process.read_pointer(self.base_address, module.pointer_size)?;
426 for &i in &cache.offsets[..x] {
427 addr = process.read_pointer(addr + i, module.pointer_size)?;
428 }
429 addr
430 }
431 },
432 };
433
434 for i in cache.resolved_offsets..self.depth {
435 let offset_from_string = match self.fields[i].strip_prefix("0x") {
436 Some(rem) => u32::from_str_radix(rem, 16).ok(),
437 _ => self.fields[i].parse().ok(),
438 };
439
440 let current_offset = match offset_from_string {
441 Some(offset) => offset as u64,
442 _ => current_uobject
443 .get_field_offset(process, module, self.fields[i])
444 .ok_or(Error {})? as u64,
445 };
446
447 cache.offsets[i] = current_offset;
448 cache.resolved_offsets += 1;
449
450 current_uobject = UObject {
451 object: process
452 .read_pointer(current_uobject.object + current_offset, module.pointer_size)?,
453 };
454 }
455 Ok(())
456 }
457
458 pub fn deref_offsets(&self, process: &Process, module: &Module) -> Result<Address, Error> {
460 self.find_offsets(process, module)?;
461 let cache = self.cache.borrow();
462 let (&last, path) = cache.offsets[..self.depth].split_last().ok_or(Error {})?;
463 let mut address = process.read_pointer(self.base_address, module.pointer_size)?;
464 for &offset in path {
465 address = process.read_pointer(address + offset, module.pointer_size)?;
466 }
467 Ok(address + last)
468 }
469
470 pub fn deref<T: CheckedBitPattern>(
472 &self,
473 process: &Process,
474 module: &Module,
475 ) -> Result<T, Error> {
476 self.find_offsets(process, module)?;
477 let cache = self.cache.borrow();
478 process.read_pointer_path(
479 process.read_pointer(self.base_address, module.pointer_size)?,
480 module.pointer_size,
481 &cache.offsets[..self.depth],
482 )
483 }
484}
485
486struct Offsets {
487 uobject_fname: u8,
488 uobject_class: u8,
489 uclass_super_field: u8,
490 uclass_property_link: u8,
491 uproperty_fname: u8,
492 uproperty_offset_internal: u8,
493 uproperty_property_link_next: u8,
494}
495
496impl Offsets {
497 const fn new(version: Version, pointer_size: PointerSize) -> Option<&'static Self> {
498 match pointer_size {
499 PointerSize::Bit64 => Some(match version {
500 Version::V4_23 | Version::V4_24 => &Self {
502 uobject_fname: 0x18,
503 uobject_class: 0x10,
504 uclass_super_field: 0x40,
505 uclass_property_link: 0x48,
506 uproperty_fname: 0x18,
507 uproperty_offset_internal: 0x44,
508 uproperty_property_link_next: 0x50,
509 },
510 Version::V4_25
512 | Version::V4_26
513 | Version::V4_27
514 | Version::V5_0
515 | Version::V5_1
516 | Version::V5_2 => &Self {
517 uobject_fname: 0x18,
518 uobject_class: 0x10,
519 uclass_super_field: 0x40,
520 uclass_property_link: 0x50,
521 uproperty_fname: 0x28,
522 uproperty_offset_internal: 0x4C,
523 uproperty_property_link_next: 0x58,
524 },
525 Version::V5_3 | Version::V5_4 => &Self {
527 uobject_fname: 0x18,
528 uobject_class: 0x10,
529 uclass_super_field: 0x40,
530 uclass_property_link: 0x50,
531 uproperty_fname: 0x20,
532 uproperty_offset_internal: 0x44,
533 uproperty_property_link_next: 0x48,
534 },
535 }),
536 _ => None,
537 }
538 }
539}
540
541#[non_exhaustive]
542#[derive(Copy, Clone, PartialEq, Hash, Debug, PartialOrd)]
543#[allow(missing_docs)]
544pub enum Version {
546 V4_23,
547 V4_24,
548 V4_25,
549 V4_26,
550 V4_27,
551 V5_0,
552 V5_1,
553 V5_2,
554 V5_3,
555 V5_4,
556}