diff --git a/Cargo.toml b/Cargo.toml index c377111..4de21cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,3 @@ [workspace] -members = ["sdk-serializer", "unreal-sdk", "sdk-generator"] \ No newline at end of file +members = ["sdk-serializer", "unreal-sdk", "sdk-generator", "pdb-helper"] +resolver = "2" \ No newline at end of file diff --git a/pdb-helper/Cargo.toml b/pdb-helper/Cargo.toml new file mode 100644 index 0000000..8b95144 --- /dev/null +++ b/pdb-helper/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "pdb-helper" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +log = "0.4.0" +anyhow = "1.0" +once_cell = "1.17.1" +pdb = "0.8.0" + +[dependencies.windows] +version = "0.44" +features = [ +"Win32_Foundation", +"Win32_System_Threading", +"Win32_System_LibraryLoader", +"Win32_UI_WindowsAndMessaging", +] \ No newline at end of file diff --git a/pdb-helper/src/lib.rs b/pdb-helper/src/lib.rs new file mode 100644 index 0000000..56cc345 --- /dev/null +++ b/pdb-helper/src/lib.rs @@ -0,0 +1,189 @@ +use std::{ptr::NonNull, sync::Mutex}; + +use anyhow::Context; +use once_cell::sync::OnceCell; +use pdb::{DataSymbol, FallibleIterator, ProcedureReferenceSymbol, ProcedureSymbol, SymbolData}; +use windows::{core::PCWSTR, Win32::System::LibraryLoader::GetModuleHandleW}; + +#[derive(Debug, Clone, Copy)] +pub struct Globals { + pub names: NonNull<()>, + pub objects: NonNull<()>, + pub process_event: usize, +} + +static PDB: OnceCell> = OnceCell::new(); + +pub fn get_pdb() -> std::sync::MutexGuard<'static, PdbCtx> { + PDB.get_or_init(|| Mutex::new(PdbCtx::new().expect("failed to create PDB helper."))) + .lock() + .unwrap() +} + +#[derive(Debug)] +pub struct PdbCtx { + base: usize, + pdb: pdb::PDB<'static, std::fs::File>, +} + +// i dont care i need this to be safe and its stored as a dyn so any Send trait is removed +unsafe impl Send for PdbCtx {} + +impl PdbCtx { + pub fn new() -> anyhow::Result { + let base = unsafe { + GetModuleHandleW(PCWSTR::null()) + .context("could not get process base")? + .0 as usize + }; + + let pdb_path = std::env::current_exe() + .context("current exe")? + .with_file_name("ShooterGame.pdb"); + let pdb_file = std::fs::File::open(&pdb_path).context("pdb file open")?; + + let pdb = pdb::PDB::open(pdb_file).context("parse pdb file")?; + + Ok(Self { base, pdb }) + } + + /// returns the address of the function + pub fn find_function(&mut self, function_name: &str) -> anyhow::Result { + let globals = self.pdb.global_symbols().context("global symbols")?; + let mut iter = globals.iter(); + + while let Some(symbol) = iter.next()? { + match symbol.parse() { + Ok(pdb::SymbolData::ProcedureReference(data)) + if data.name.map(|name| name.to_string() == function_name) == Some(true) => + { + log::trace!("{data:?}"); + let dbg_info = self.pdb.debug_information()?; + let mods = dbg_info + .modules()? + .nth(data.module.context("module idx none")?) + .context("module not found")? + .context("module found but none")?; + let a = self + .pdb + .module_info(&mods) + .context("module info not found")? + .context("module info found but none")?; + let sym = a + .symbols_at(data.symbol_index) + .context("symbol not found")? + .next() + .context("symbol failed to read")? + .context("symbol read but none")? + .parse() + .context("symbol failed to parse")?; + + log::debug!("actual symbol: {sym:?}"); + + if let SymbolData::Procedure(ProcedureSymbol { offset, .. }) = sym { + // asdf + let get_names = offset + .to_rva(&self.pdb.address_map()?) + .context("rva none")?; + return Ok(self.base + get_names.0 as usize); + } else { + return Err(anyhow::anyhow!("no proceduresymbol found")); + } + } + _ => {} + } + } + return Err(anyhow::anyhow!("no procedureref found")); + } + + pub fn parse_names(&mut self, data: ProcedureReferenceSymbol) -> anyhow::Result { + log::trace!("{data:?}"); + let dbg_info = self.pdb.debug_information()?; + let mods = dbg_info + .modules()? + .nth(data.module.context("module idx none")?) + .context("module not found")? + .context("module found but none")?; + let a = self + .pdb + .module_info(&mods) + .context("module info not found")? + .context("module info found but none")?; + let sym = a + .symbols_at(data.symbol_index) + .context("symbol not found")? + .next() + .context("symbol failed to read")? + .context("symbol read but none")? + .parse() + .context("symbol failed to parse")?; + + log::debug!("actual symbol: {sym:?}"); + + if let SymbolData::Procedure(ProcedureSymbol { offset, .. }) = sym { + // asdf + let get_names = offset + .to_rva(&self.pdb.address_map()?) + .context("rva none")?; + let get_names = unsafe { + core::mem::transmute::<_, unsafe extern "win64" fn() -> *mut ()>( + self.base + get_names.0 as usize, + ) + }; + + let names = unsafe { get_names() }; + + log::debug!("names: {:?}", names); + + Ok(names as usize) + } else { + Err(anyhow::anyhow!("no proceduresymbol found")) + } + } + + pub fn parse_objects(&mut self, data: DataSymbol) -> anyhow::Result { + let get_names = data + .offset + .to_rva(&self.pdb.address_map()?) + .context("rva none")?; + let objects = self.base + get_names.0 as usize; + + Ok(objects) + } + + pub fn find_ue_globals(&mut self) -> anyhow::Result { + let globals = self.pdb.global_symbols().context("global symbols")?; + let mut iter = globals.iter(); + + let mut names = None; + let mut objects = None; + + while let Some(symbol) = iter.next()? { + match symbol.parse() { + Ok(pdb::SymbolData::Data(data)) if data.name.to_string() == "GUObjectArray" => { + objects = Some(self.parse_objects(data)?); + } + Ok(pdb::SymbolData::ProcedureReference(data)) + if data.name.map(|name| name.to_string() == "FName::GetNames") + == Some(true) => + { + names = Some(self.parse_names(data)?); + } + _ => {} + } + } + + let process_event = self + .find_function("UObject::ProcessEvent") + .context("could not find UObject::ProcessEvent!")?; + + let names = names.context("could not find names!")?; + let objects = objects.context("could not find objects!")?; + + Ok(Globals { + names: NonNull::new(names as _).context("GNames was null!")?, + objects: NonNull::new(objects as _).context("GObjects was null!")?, + process_event, + }) + } +}