mirror of
https://github.com/a2x/cs2-dumper.git
synced 2025-10-08 02:00:02 +08:00
Release 1.1.4
This commit is contained in:
149
src/dumper/interfaces.rs
Normal file
149
src/dumper/interfaces.rs
Normal 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
154
src/dumper/mod.rs
Normal 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
256
src/dumper/offsets.rs
Normal 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
80
src/dumper/schemas.rs
Normal 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(())
|
||||
}
|
Reference in New Issue
Block a user