Added unit tests

This commit is contained in:
a2x 2023-10-14 15:18:52 +10:00
parent acf11ea6c1
commit df7a1d75ca
7 changed files with 93 additions and 23 deletions

View File

@ -3,6 +3,7 @@ name = "cs2-dumper"
version = "1.1.0" version = "1.1.0"
authors = ["a2x"] authors = ["a2x"]
edition = "2021" edition = "2021"
readme = "README.md"
repository = "https://github.com/a2x/cs2-dumper" repository = "https://github.com/a2x/cs2-dumper"
license = "MIT" license = "MIT"

View File

@ -1,6 +1,6 @@
# CS2 Dumper # cs2-dumper
Tool to automatically dump offsets and interfaces for Counter-Strike: 2. External offsets/interfaces dumper for Counter-Strike: 2, written in Rust.
# Generated Files # Generated Files
@ -8,6 +8,10 @@ Generated files are stored in the `generated` directory.
📂 [Pre-generated Files](./generated) 📂 [Pre-generated Files](./generated)
# Running Tests
`cargo test -- --nocapture`
# License # License
Please refer to the [LICENSE](./LICENSE) file for more details. Please refer to the [LICENSE](./LICENSE) file for more details.

View File

@ -25,34 +25,36 @@
{ {
"name": "dwForceAttack", "name": "dwForceAttack",
"module": "client.dll", "module": "client.dll",
"pattern": "48 8B 15 ? ? ? ? 48 8D 0D ? ? ? ? E9 7D 69 FD 00", "pattern": "48 8D 0D ? ? ? ? E9 D4 4B B4 FF",
"operations": [ "operations": [
{ {
"type": "ripRelative" "type": "ripRelative"
}, },
{ {
"type": "dereference" "type": "subtract",
"value": 8
}, },
{ {
"type": "add", "type": "add",
"value": 440 "value": 48
} }
] ]
}, },
{ {
"name": "dwForceAttack2", "name": "dwForceAttack2",
"module": "client.dll", "module": "client.dll",
"pattern": "48 8B 15 ? ? ? ? 48 8D 0D ? ? ? ? E9 7D 69 FD 00", "pattern": "48 8D 0D ? ? ? ? E9 E4 4B B4 FF",
"operations": [ "operations": [
{ {
"type": "ripRelative" "type": "ripRelative"
}, },
{ {
"type": "dereference" "type": "subtract",
"value": 8
}, },
{ {
"type": "add", "type": "add",
"value": 584 "value": 48
} }
] ]
}, },

View File

@ -36,7 +36,7 @@ pub fn dump_interfaces(builders: &mut Vec<FileBuilderEnum>, process: &Process) -
interface_version, interface_version,
interface_ptr, interface_ptr,
module_name, module_name,
interface_ptr - module.address() interface_ptr - module.base()
); );
entries entries
@ -44,7 +44,7 @@ pub fn dump_interfaces(builders: &mut Vec<FileBuilderEnum>, process: &Process) -
.or_default() .or_default()
.push(Entry { .push(Entry {
name: interface_version.clone(), name: interface_version.clone(),
value: interface_ptr - module.address(), value: interface_ptr - module.base(),
comment: None, comment: None,
}); });

View File

@ -9,6 +9,69 @@ use crate::remote::Process;
use super::{generate_files, Entries}; use super::{generate_files, Entries};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn build_number() -> Result<()> {
let process = Process::new("cs2.exe")?;
let engine_base = process.get_module_by_name("engine2.dll")?.base();
let build_number = process.read_memory::<u32>(engine_base + 0x487514)?;
println!("Build number: {}", build_number);
Ok(())
}
#[test]
fn global_vars() -> Result<()> {
let process = Process::new("cs2.exe")?;
let client_base = process.get_module_by_name("client.dll")?.base();
let global_vars = process.read_memory::<usize>(client_base + 0x1692EE8)?;
let current_map_name =
process.read_string(process.read_memory::<usize>(global_vars + 0x188)?)?;
println!("Current map name: {}", current_map_name);
Ok(())
}
#[test]
fn local_player() -> Result<()> {
let process = Process::new("cs2.exe")?;
let client_base = process.get_module_by_name("client.dll")?.base();
let local_player_controller = process.read_memory::<usize>(client_base + 0x17DE508)?;
let player_name = process.read_string(local_player_controller + 0x610)?;
println!("Name: {}", player_name);
Ok(())
}
#[test]
fn window_size() -> Result<()> {
let process = Process::new("cs2.exe")?;
let engine_base = process.get_module_by_name("engine2.dll")?.base();
let window_width = process.read_memory::<u32>(engine_base + 0x538670)?;
let window_height = process.read_memory::<u32>(engine_base + 0x538674)?;
println!("Window size: {}x{}", window_width, window_height);
Ok(())
}
}
pub fn dump_offsets(builders: &mut Vec<FileBuilderEnum>, process: &Process) -> Result<()> { pub fn dump_offsets(builders: &mut Vec<FileBuilderEnum>, process: &Process) -> Result<()> {
let file = File::open("config.json")?; let file = File::open("config.json")?;
@ -60,7 +123,7 @@ pub fn dump_offsets(builders: &mut Vec<FileBuilderEnum>, process: &Process) -> R
} }
} }
let (name, value) = if address.0 < module.address() { let (name, value) = if address.0 < module.base() {
log::debug!(" └─ {} @ {:#X}", signature.name, address.0); log::debug!(" └─ {} @ {:#X}", signature.name, address.0);
(signature.name, address.0) (signature.name, address.0)
@ -70,10 +133,10 @@ pub fn dump_offsets(builders: &mut Vec<FileBuilderEnum>, process: &Process) -> R
signature.name, signature.name,
address, address,
signature.module, signature.module,
address.sub(module.address()) address.sub(module.base())
); );
(signature.name, address.sub(module.address()).0) (signature.name, address.sub(module.base()).0)
}; };
entries entries

View File

@ -24,7 +24,7 @@ pub struct Section {
} }
pub struct Module<'a> { pub struct Module<'a> {
address: usize, base: usize,
nt_headers: &'a IMAGE_NT_HEADERS64, nt_headers: &'a IMAGE_NT_HEADERS64,
size: u32, size: u32,
exports: Vec<Export>, exports: Vec<Export>,
@ -32,10 +32,10 @@ pub struct Module<'a> {
} }
impl<'a> Module<'a> { impl<'a> Module<'a> {
pub fn new(process: &'a Process, address: usize) -> Result<Self> { pub fn new(process: &'a Process, base: usize) -> Result<Self> {
let mut headers: [u8; 0x1000] = [0; 0x1000]; let mut headers: [u8; 0x1000] = [0; 0x1000];
process.read_memory_raw(address, headers.as_mut_ptr() as *mut _, headers.len())?; process.read_memory_raw(base, headers.as_mut_ptr() as *mut _, headers.len())?;
if headers.len() < mem::size_of::<IMAGE_DOS_HEADER>() { if headers.len() < mem::size_of::<IMAGE_DOS_HEADER>() {
return Err(Error::BufferSizeMismatch( return Err(Error::BufferSizeMismatch(
@ -60,11 +60,11 @@ impl<'a> Module<'a> {
let size = nt_headers.OptionalHeader.SizeOfImage; let size = nt_headers.OptionalHeader.SizeOfImage;
let exports = unsafe { Self::parse_exports(process, address, size, nt_headers)? }; let exports = unsafe { Self::parse_exports(process, base, size, nt_headers)? };
let sections = unsafe { Self::parse_sections(address, nt_headers) }; let sections = unsafe { Self::parse_sections(base, nt_headers) };
Ok(Self { Ok(Self {
address, base,
nt_headers, nt_headers,
size, size,
exports, exports,
@ -73,8 +73,8 @@ impl<'a> Module<'a> {
} }
#[inline] #[inline]
pub fn address(&self) -> usize { pub fn base(&self) -> usize {
self.address self.base
} }
#[inline] #[inline]

View File

@ -35,7 +35,7 @@ impl Process {
let mut module_data: Vec<u8> = vec![0; module.size() as usize]; let mut module_data: Vec<u8> = vec![0; module.size() as usize];
self.read_memory_raw( self.read_memory_raw(
module.address(), module.base(),
module_data.as_mut_ptr() as *mut _, module_data.as_mut_ptr() as *mut _,
module_data.len(), module_data.len(),
)?; )?;
@ -54,7 +54,7 @@ impl Process {
} }
if found { if found {
return Ok(module.address() + i); return Ok(module.base() + i);
} }
} }