Release 1.1.4

This commit is contained in:
a2x
2023-10-26 15:41:34 +10:00
parent 631668429c
commit 239c872b65
37 changed files with 1905 additions and 1308 deletions

149
src/dumper/interfaces.rs Normal file
View File

@@ -0,0 +1,149 @@
use super::{generate_files, Entries, Entry};
use crate::builder::FileBuilderEnum;
use crate::util::{Address, Process};
use anyhow::Result;
use simplelog::{debug, info};
use std::ffi::c_char;
use std::mem::offset_of;
/// Represents a node in a linked list of interfaces.
#[derive(Debug)]
#[repr(C)]
struct InterfaceNode {
/// Function pointer used to instantiate an instance of the interface.
pub create_fn: *const (),
/// Pointer to the name of the interface.
pub name: *const c_char,
/// Pointer to the next entry in the linked list.
pub next: *mut InterfaceNode,
}
impl InterfaceNode {
/// Returns the instance of the interface.
///
/// # Arguments
///
/// * `&self` - A reference to the `InterfaceNode` struct.
/// * `process` - A reference to the `Process` struct.
///
/// # Returns
///
/// * `Result<Address>` - A `Result` containing the instance of the interface if successful, or an error if the memory read fails.
fn instance(&self, process: &Process) -> Result<Address> {
process
.read_memory::<usize>(
(self as *const _ as usize + offset_of!(InterfaceNode, create_fn)).into(),
)
.map(|ptr| ptr.into())
}
/// Returns the name of the interface.
///
/// E.g. "Source2Client002".
///
/// # Arguments
///
/// * `&self` - A reference to the `InterfaceNode` struct.
/// * `process` - A reference to the `Process` struct.
///
/// # Returns
///
/// * `Result<String>` - A `Result` containing the name of the interface if successful, or an error if the memory read fails.
fn name(&self, process: &Process) -> Result<String> {
let name_ptr = process.read_memory::<usize>(
(self as *const _ as usize + offset_of!(InterfaceNode, name)).into(),
)?;
process.read_string(name_ptr.into())
}
/// Returns a pointer to the next `InterfaceNode` in the linked list.
///
/// # Arguments
///
/// * `&self` - A reference to the `InterfaceNode` struct.
/// * `process` - A reference to the `Process` struct.
///
/// # Returns
///
/// * `Result<*mut InterfaceNode>` - A `Result` containing a pointer to the next `InterfaceNode` if successful, or an error if the memory read fails.
fn next(&self, process: &Process) -> Result<*mut InterfaceNode> {
process.read_memory::<*mut InterfaceNode>(
(self as *const _ as usize + offset_of!(InterfaceNode, next)).into(),
)
}
}
/// Dumps all interfaces and writes the results to a file.
///
/// # Arguments
///
/// * `process` - A reference to the `Process` struct.
/// * `builders` - A mutable reference to a vector of `FileBuilderEnum`.
/// * `file_path` - A string slice representing the path to the file to write the results to.
/// * `indent` - The number of spaces to use for indentation in the output file.
///
/// # Returns
///
/// * `Result<()>` - A `Result` indicating the outcome of the operation.
pub fn dump_interfaces(
process: &Process,
builders: &mut Vec<FileBuilderEnum>,
file_path: &str,
indent: usize,
) -> Result<()> {
let mut entries = Entries::new();
for module in process
.modules()?
.iter()
.filter(|m| m.name != "crashhandler64.dll")
{
if let Some(create_interface_export) = module.get_export_by_name("CreateInterface") {
info!("Dumping interfaces in <i><blue>{}</></>...", module.name);
let create_interface_address =
process.resolve_rip(create_interface_export, 0x3, 0x7)?;
if let Ok(mut node) =
process.read_memory::<*mut InterfaceNode>(create_interface_address)
{
while !node.is_null() {
let instance = unsafe { (*node).instance(process) }?;
let name = unsafe { (*node).name(process) }?;
debug!(
"Found <i><bright-yellow>{}</></> @ <bright-magenta>{:#X}</> (<i><blue>{}</></> + <bright-blue>{:#X}</>)",
name,
instance,
module.name,
instance - module.base()
);
let container = entries.entry(module.name.replace(".", "_")).or_default();
container.comment = Some(module.name.to_string());
container.data.push(Entry {
name,
value: (instance - module.base()).into(),
comment: None,
indent: Some(indent),
});
node = unsafe { (*node).next(process) }?;
}
}
}
}
generate_files(builders, &entries, file_path, "interfaces")?;
Ok(())
}

154
src/dumper/mod.rs Normal file
View File

@@ -0,0 +1,154 @@
use crate::builder::{FileBuilder, FileBuilderEnum};
use anyhow::Result;
use chrono::Utc;
pub use interfaces::dump_interfaces;
pub use offsets::dump_offsets;
pub use schemas::dump_schemas;
use std::collections::BTreeMap;
use std::fs::File;
use std::io::Write;
pub mod interfaces;
pub mod offsets;
pub mod schemas;
/// Represents an entry in the generated file.
#[derive(Debug, PartialEq)]
pub struct Entry {
/// The name of the entry.
pub name: String,
/// The value of the entry.
pub value: usize,
/// An optional comment associated with the entry.
pub comment: Option<String>,
/// An optional indentation level for the entry.
pub indent: Option<usize>,
}
/// A container for entries, which consists of data and an optional comment.
#[derive(Default)]
pub struct EntriesContainer {
/// The data associated with the container.
pub data: Vec<Entry>,
/// An optional comment associated with the container.
pub comment: Option<String>,
}
/// A type alias for a `BTreeMap` that maps `String` keys to `EntriesContainer` values.
pub type Entries = BTreeMap<String, EntriesContainer>;
/// Generates a file using the given `builder`, `entries`, `file_path`, and `file_name`.
///
/// # Arguments
///
/// * `builder` - A mutable reference to the `FileBuilderEnum`.
/// * `entries` - A reference to the `Entries` struct.
/// * `file_path` - A string slice representing the path to the file.
/// * `file_name` - A string slice representing the name of the file.
///
/// # Returns
///
/// * `Result<()>` - A `Result` indicating the outcome of the operation.
pub fn generate_file(
builder: &mut FileBuilderEnum,
entries: &Entries,
file_path: &str,
file_name: &str,
) -> Result<()> {
if entries.is_empty() {
return Ok(());
}
let file_path = format!("{}/{}.{}", file_path, file_name, builder.extension());
let mut file = File::create(file_path)?;
write_banner_to_file(&mut file, builder.extension())?;
builder.write_top_level(&mut file)?;
let len = entries.len();
for (i, pair) in entries.iter().enumerate() {
builder.write_namespace(&mut file, pair.0, pair.1.comment.as_deref())?;
pair.1.data.iter().try_for_each(|entry| {
builder.write_variable(
&mut file,
&entry.name,
entry.value,
entry.comment.as_deref(),
entry.indent,
)
})?;
builder.write_closure(&mut file, i == len - 1)?;
}
Ok(())
}
/// Generate files using the given `builders`, `entries`, `file_path`, and `file_name`.
///
/// # Arguments
///
/// * `builders` - A mutable slice of `FileBuilderEnum` objects.
/// * `entries` - A reference to the `Entries` struct.
/// * `file_path` - A string slice representing the path to the file.
/// * `file_name` - A string slice representing the name of the file.
///
/// # Returns
///
/// * `Result<()>` - A `Result` indicating the outcome of the operation.
pub fn generate_files(
builders: &mut [FileBuilderEnum],
entries: &Entries,
file_path: &str,
file_name: &str,
) -> Result<()> {
builders
.iter_mut()
.try_for_each(|builder| generate_file(builder, entries, file_path, file_name))
}
/// Writes the banner to the given file based on the file extension.
///
/// # Arguments
///
/// * `file` - A mutable reference to the file to write the banner to.
/// * `file_extension` - A string slice representing the file extension of the file.
///
/// # Returns
///
/// * `Result<()>` - A `Result` indicating the outcome of the operation.
fn write_banner_to_file(file: &mut File, file_extension: &str) -> Result<()> {
const REPO_URL: &str = "https://github.com/a2x/cs2-dumper";
let time_now = Utc::now().to_rfc2822();
let banner = match file_extension {
"json" => None,
"py" => Some(format!(
"'''\nCreated using {}\n{}\n'''\n\n",
REPO_URL, time_now
)),
_ => Some(format!(
"/*\n * Created using {}\n * {}\n */\n\n",
REPO_URL, time_now
)),
};
if let Some(banner) = banner {
write!(file, "{}", banner)?;
}
Ok(())
}

256
src/dumper/offsets.rs Normal file
View File

@@ -0,0 +1,256 @@
use super::{generate_files, Entries, Entry};
use crate::builder::FileBuilderEnum;
use crate::config::Config;
use crate::config::Operation::*;
use crate::util::Process;
use anyhow::Result;
use simplelog::{debug, error, info};
use std::fs::File;
// Dumps all offsets specified in the `config.json` file and writes the results to a file.
///
/// # Arguments
///
/// * `process` - A reference to the `Process` struct.
/// * `builders` - A mutable reference to a vector of `FileBuilderEnum`.
/// * `file_path` - A string slice representing the path to the file to write the results to.
/// * `indent` - The number of spaces to use for indentation in the output file.
///
/// # Returns
///
/// * `Result<()>` - A `Result` indicating the outcome of the operation.
pub fn dump_offsets(
process: &Process,
builders: &mut Vec<FileBuilderEnum>,
file_path: &str,
indent: usize,
) -> Result<()> {
let file = File::open("config.json")?;
let config: Config = serde_json::from_reader(file)?;
info!("Dumping offsets...");
let mut entries = Entries::new();
for signature in config.signatures {
debug!(
"Searching for <i><bright-yellow>{}</></>...",
signature.name
);
let module = process
.get_module_by_name(&signature.module)
.expect(&format!("Failed to find module {}.", signature.module));
let mut address = match process.find_pattern(&signature.module, &signature.pattern) {
Some(a) => a,
None => {
error!(
"Failed to find pattern for <i><bright-yellow>{}</></>.",
signature.name
);
continue;
}
};
for operation in signature.operations {
match operation {
Add { value } => address += value,
Dereference { times, size } => {
let times = times.unwrap_or(1);
let size = size.unwrap_or(8);
for _ in 0..times {
process.read_memory_raw(
address,
&mut address.0 as *mut _ as *mut _,
size,
)?;
}
}
Jmp { offset, length } => {
address = process
.resolve_jmp(address, offset.unwrap_or(0x1), length.unwrap_or(0x5))?
.into()
}
RipRelative { offset, length } => {
address = process
.resolve_rip(address, offset.unwrap_or(0x3), length.unwrap_or(0x7))?
.into()
}
Slice { start, end } => {
let mut result: usize = 0;
process.read_memory_raw(
address.add(start),
&mut result as *mut _ as *mut _,
end - start,
)?;
address = result.into();
}
Subtract { value } => address -= value,
}
}
let (name, value) = if address < module.base() {
debug!(
"Found <i><bright-yellow>{}</></> @ <bright-blue>{:#X}</>",
signature.name, address
);
(signature.name, address.0)
} else {
debug!(
"Found <i><bright-yellow>{}</></> @ <bright-magenta>{:#X}</> (<i><blue>{}</></> + <bright-blue>{:#X}</>)",
signature.name,
address,
signature.module,
address.sub(module.base().0)
);
(signature.name, address.sub(module.base().0).0)
};
let container = entries
.entry(signature.module.replace(".", "_"))
.or_default();
container.comment = Some(signature.module);
container.data.push(Entry {
name,
value,
comment: None,
indent: Some(indent),
});
}
generate_files(builders, &entries, file_path, "offsets")?;
Ok(())
}
#[cfg(test)]
mod tests {
use std::ffi::c_char;
use std::mem::offset_of;
use super::*;
#[test]
fn build_number() -> Result<()> {
let process = Process::new("cs2.exe")?;
let engine_base = process
.get_module_by_name("engine2.dll")
.expect("Failed to find engine2.dll")
.base();
let build_number = process.read_memory::<u32>(engine_base + 0x487514)?;
println!("Build number: {}", build_number);
Ok(())
}
#[test]
fn global_vars() -> Result<()> {
#[derive(Debug)]
#[repr(C)]
struct GlobalVarsBase {
real_time: f32, // 0x0000
frame_count: i32, // 0x0004
pad_0: [u8; 0x8], // 0x0008
max_clients: i32, // 0x0010
interval_per_tick: f32, // 0x0014
pad_1: [u8; 0x14], // 0x0018
current_time: f32, // 0x002C
current_time2: f32, // 0x0030
pad_2: [u8; 0xC], // 0x0034
tick_count: i32, // 0x0040
interval_per_tick2: f32, // 0x0044
pad_3: [u8; 0x138], // 0x0048
current_map: *const c_char, // 0x0180
current_map_name: *const c_char, // 0x0188
}
impl GlobalVarsBase {
fn current_map(&self, process: &Process) -> Result<String> {
let name_ptr = process.read_memory::<usize>(
(self as *const _ as usize + offset_of!(Self, current_map)).into(),
)?;
process.read_string(name_ptr.into())
}
fn current_map_name(&self, process: &Process) -> Result<String> {
let name_ptr = process.read_memory::<usize>(
(self as *const _ as usize + offset_of!(Self, current_map_name)).into(),
)?;
process.read_string(name_ptr.into())
}
}
let process = Process::new("cs2.exe")?;
let client_base = process
.get_module_by_name("client.dll")
.expect("Failed to find client.dll")
.base();
let global_vars = process.read_memory::<*const GlobalVarsBase>(client_base + 0x1696F40)?;
let current_map_name = unsafe {
(*global_vars)
.current_map_name(&process)
.unwrap_or_default()
};
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")
.expect("Failed to find client.dll")
.base();
let local_player_controller = process.read_memory::<usize>(client_base + 0x17E27C8)?;
let player_name = process.read_string((local_player_controller + 0x610).into())?;
println!("Local player 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")
.expect("Failed to find engine2.dll")
.base();
let window_width = process.read_memory::<u32>(engine_base + 0x5386D0)?;
let window_height = process.read_memory::<u32>(engine_base + 0x5386D4)?;
println!("Window size: {}x{}", window_width, window_height);
Ok(())
}
}

80
src/dumper/schemas.rs Normal file
View File

@@ -0,0 +1,80 @@
use super::{generate_files, Entries, Entry};
use crate::builder::FileBuilderEnum;
use crate::sdk::SchemaSystem;
use crate::util::Process;
use anyhow::Result;
use simplelog::{debug, info};
// Dumps all schema system defined classes and writes the results to a file.
///
/// # Arguments
///
/// * `process` - A reference to the `Process` struct.
/// * `builders` - A mutable reference to a vector of `FileBuilderEnum`.
/// * `file_path` - A string slice representing the path to the file to write the results to.
/// * `indent` - The number of spaces to use for indentation in the output file.
///
/// # Returns
///
/// * `Result<()>` - A `Result` indicating the outcome of the operation.
pub fn dump_schemas(
process: &Process,
builders: &mut Vec<FileBuilderEnum>,
file_path: &str,
indent: usize,
) -> Result<()> {
let schema_system = SchemaSystem::new(&process)?;
for type_scope in schema_system.type_scopes()? {
let module_name = type_scope.module_name()?;
info!("Generating files for <i><blue>{}</></>...", module_name);
let mut entries = Entries::new();
for class in type_scope.classes()? {
let parent_name = match class.parent()?.map(|p| p.name().to_string()) {
Some(name) => name,
None => continue,
};
debug!(
"<i><u><bright-yellow>{}</></></> : <i><u><yellow>{}</></></>",
class.name(),
parent_name
);
let container = entries.entry(class.name().replace("::", "_")).or_default();
container.comment = Some(parent_name);
for field in class.fields()? {
let name = field.name()?;
let offset = field.offset()?;
let type_name = field.r#type()?.name()?;
debug!(
"{}<bright-yellow>{}</> = <bright-blue>{:#X}</> // <b><cyan>{}</></>",
" ".repeat(indent),
name,
offset,
type_name
);
container.data.push(Entry {
name,
value: offset as usize,
comment: Some(type_name),
indent: Some(indent),
});
}
}
generate_files(builders, &entries, file_path, &module_name)?;
}
Ok(())
}