mirror of
https://github.com/a2x/cs2-dumper.git
synced 2025-04-02 12:15:35 +08:00
Release 1.1.4
This commit is contained in:
parent
631668429c
commit
239c872b65
1
.gitattributes
vendored
1
.gitattributes
vendored
@ -1,4 +1,5 @@
|
||||
*.cs linguist-detectable=false
|
||||
*.hpp linguist-detectable=false
|
||||
*.json linguist-detectable=false
|
||||
*.py linguist-detectable=false
|
||||
*.rs linguist-detectable=true
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
.idea/
|
||||
.vscode/
|
||||
Cargo.lock
|
||||
target/
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "cs2-dumper"
|
||||
version = "1.1.3"
|
||||
version = "1.1.4"
|
||||
authors = ["a2x"]
|
||||
edition = "2021"
|
||||
readme = "README.md"
|
||||
@ -11,13 +11,13 @@ license = "MIT"
|
||||
anyhow = "1.0"
|
||||
chrono = "0.4"
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
convert_case = "0.6"
|
||||
goblin = { git = "https://github.com/m4b/goblin" }
|
||||
lazy_static = "1.4"
|
||||
log = "0.4"
|
||||
regex = "1.9"
|
||||
regex = "1.10"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
simple_logger = "4.2"
|
||||
simplelog = { version = "0.12", features = ["paris"] }
|
||||
|
||||
[dependencies.windows]
|
||||
version = "0.51"
|
||||
|
17
README.md
17
README.md
@ -1,17 +1,24 @@
|
||||
# cs2-dumper
|
||||
|
||||
External offsets/interfaces dumper for Counter-Strike: 2, written in Rust.
|
||||
An external offsets/interfaces dumper for Counter-Strike 2, written in Rust.
|
||||
|
||||
# Usage
|
||||
|
||||
You can either download the latest release from [Releases](https://github.com/a2x/cs2-dumper/releases) or build it yourself. Note that building it yourself requires Rust's nightly toolchain.
|
||||
|
||||
If you want to see more detailed runtime messages, you can pass the `--verbose` flag.
|
||||
|
||||
For a complete list of all available flags, use `--help`.
|
||||
|
||||
# Generated Files
|
||||
|
||||
Generated files are stored in the `generated` directory.
|
||||
By default, processed files are stored in the `generated` directory. However, this can be modified by specifying your desired output folder using the `--output` flag.
|
||||
|
||||
📂 [Pre-generated Files](./generated)
|
||||
|
||||
# Running Tests
|
||||
|
||||
`cargo test -- --nocapture`
|
||||
To run tests, use the following command: `cargo test -- --nocapture`.
|
||||
|
||||
# License
|
||||
|
||||
Please refer to the [LICENSE](./LICENSE) file for more details.
|
||||
Please refer to the [LICENSE](./LICENSE) file for more details.
|
@ -1,8 +1,9 @@
|
||||
use std::io::{Result, Write};
|
||||
|
||||
use super::FileBuilder;
|
||||
|
||||
/// Represents a C++ header file builder.
|
||||
use std::io::{Result, Write};
|
||||
|
||||
/// A structure representing a builder for C++ header files.
|
||||
/// The builder implements the `FileBuilder` trait.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct CppFileBuilder;
|
||||
|
||||
@ -12,10 +13,7 @@ impl FileBuilder for CppFileBuilder {
|
||||
}
|
||||
|
||||
fn write_top_level(&mut self, output: &mut dyn Write) -> Result<()> {
|
||||
write!(output, "#pragma once\n\n")?;
|
||||
write!(output, "#include <cstddef>\n\n")?;
|
||||
|
||||
Ok(())
|
||||
write!(output, "#pragma once\n\n#include <cstddef>\n\n")
|
||||
}
|
||||
|
||||
fn write_namespace(
|
||||
@ -24,13 +22,9 @@ impl FileBuilder for CppFileBuilder {
|
||||
name: &str,
|
||||
comment: Option<&str>,
|
||||
) -> Result<()> {
|
||||
if let Some(comment) = comment {
|
||||
write!(output, "namespace {} {{ // {}\n", name, comment)?;
|
||||
} else {
|
||||
write!(output, "namespace {} {{\n", name)?;
|
||||
}
|
||||
let comment = comment.map_or(String::new(), |c| format!("// {}", c));
|
||||
|
||||
Ok(())
|
||||
write!(output, "namespace {} {{ {}\n", name, comment)
|
||||
}
|
||||
|
||||
fn write_variable(
|
||||
@ -39,24 +33,20 @@ impl FileBuilder for CppFileBuilder {
|
||||
name: &str,
|
||||
value: usize,
|
||||
comment: Option<&str>,
|
||||
indentation: Option<usize>,
|
||||
) -> Result<()> {
|
||||
match comment {
|
||||
Some(comment) => write!(
|
||||
output,
|
||||
" constexpr std::ptrdiff_t {} = {:#X}; // {}\n",
|
||||
name, value, comment
|
||||
),
|
||||
None => write!(
|
||||
output,
|
||||
" constexpr std::ptrdiff_t {} = {:#X};\n",
|
||||
name, value
|
||||
),
|
||||
}
|
||||
let indentation = " ".repeat(indentation.unwrap_or(4));
|
||||
|
||||
let comment = comment.map_or(String::new(), |c| format!("// {}", c));
|
||||
|
||||
write!(
|
||||
output,
|
||||
"{}constexpr std::ptrdiff_t {} = {:#X}; {}\n",
|
||||
indentation, name, value, comment
|
||||
)
|
||||
}
|
||||
|
||||
fn write_closure(&mut self, output: &mut dyn Write, eof: bool) -> Result<()> {
|
||||
write!(output, "{}", if eof { "}" } else { "}\n\n" })?;
|
||||
|
||||
Ok(())
|
||||
write!(output, "{}", if eof { "}" } else { "}\n\n" })
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
use std::io::{Result, Write};
|
||||
|
||||
use super::FileBuilder;
|
||||
|
||||
/// Represents a C# file builder.
|
||||
use std::io::{Result, Write};
|
||||
|
||||
/// A structure representing a builder for C# files.
|
||||
/// The builder implements the `FileBuilder` trait.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct CSharpFileBuilder;
|
||||
|
||||
@ -21,13 +22,9 @@ impl FileBuilder for CSharpFileBuilder {
|
||||
name: &str,
|
||||
comment: Option<&str>,
|
||||
) -> Result<()> {
|
||||
if let Some(comment) = comment {
|
||||
write!(output, "public static class {} {{ // {}\n", name, comment)?;
|
||||
} else {
|
||||
write!(output, "public static class {} {{\n", name)?;
|
||||
}
|
||||
let comment = comment.map_or(String::new(), |c| format!("// {}", c));
|
||||
|
||||
Ok(())
|
||||
write!(output, "public static class {} {{ {}\n", name, comment)
|
||||
}
|
||||
|
||||
fn write_variable(
|
||||
@ -36,20 +33,20 @@ impl FileBuilder for CSharpFileBuilder {
|
||||
name: &str,
|
||||
value: usize,
|
||||
comment: Option<&str>,
|
||||
indentation: Option<usize>,
|
||||
) -> Result<()> {
|
||||
match comment {
|
||||
Some(comment) => write!(
|
||||
output,
|
||||
" public const nint {} = {:#X}; // {}\n",
|
||||
name, value, comment
|
||||
),
|
||||
None => write!(output, " public const nint {} = {:#X};\n", name, value),
|
||||
}
|
||||
let indentation = " ".repeat(indentation.unwrap_or(4));
|
||||
|
||||
let comment = comment.map_or(String::new(), |c| format!("// {}", c));
|
||||
|
||||
write!(
|
||||
output,
|
||||
"{}public const nint {} = {:#X}; {}\n",
|
||||
indentation, name, value, comment
|
||||
)
|
||||
}
|
||||
|
||||
fn write_closure(&mut self, output: &mut dyn Write, eof: bool) -> Result<()> {
|
||||
write!(output, "{}", if eof { "}" } else { "}\n\n" })?;
|
||||
|
||||
Ok(())
|
||||
write!(output, "{}", if eof { "}" } else { "}\n\n" })
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,42 @@
|
||||
use std::io::{Result, Write};
|
||||
|
||||
/// Represents a file builder.
|
||||
/// A trait that defines the file builder operations.
|
||||
pub trait FileBuilder {
|
||||
/// Returns the file extension.
|
||||
/// Returns the extension of the file.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&mut self` - A mutable reference to the `FileBuilder` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `&str` - A string slice containing the extension of the file.
|
||||
fn extension(&mut self) -> &str;
|
||||
|
||||
/// Writes the top level of the file.
|
||||
/// Write to the top level of the file. The output destination is `output`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&mut self` - A mutable reference to the `FileBuilder` struct.
|
||||
/// * `output` - An object implementing Write trait where the top level will be written.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<()>` - A generic Result type indicating the operations outcome.
|
||||
fn write_top_level(&mut self, output: &mut dyn Write) -> Result<()>;
|
||||
|
||||
/// Writes a namespace.
|
||||
/// Write a namespace to the output.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&mut self` - A mutable reference to the `FileBuilder` struct.
|
||||
/// * `output` - An object implementing Write trait where the namespace will be written.
|
||||
/// * `name` - The name of the namespace.
|
||||
/// * `comment` - An optional comment. If present, this comment will be included in the output.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<()>` - A generic Result type indicating the operations outcome.
|
||||
fn write_namespace(
|
||||
&mut self,
|
||||
output: &mut dyn Write,
|
||||
@ -16,15 +44,39 @@ pub trait FileBuilder {
|
||||
comment: Option<&str>,
|
||||
) -> Result<()>;
|
||||
|
||||
/// Writes a variable.
|
||||
/// Write a variable to the output.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&mut self` - A mutable reference to the `FileBuilder` struct.
|
||||
/// * `output` - An object implementing Write trait where the variable will be written.
|
||||
/// * `name` - The name of the variable.
|
||||
/// * `value` - The value of the variable.
|
||||
/// * `comment` - An optional comment. If present, this comment will be included in the output.
|
||||
/// * `indentation` - An optional indentation value. If present, the variable will be written with the specified indentation.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<()>` - A generic Result type indicating the operations outcome.
|
||||
fn write_variable(
|
||||
&mut self,
|
||||
output: &mut dyn Write,
|
||||
name: &str,
|
||||
value: usize,
|
||||
comment: Option<&str>,
|
||||
indentation: Option<usize>,
|
||||
) -> Result<()>;
|
||||
|
||||
/// Writes a closure.
|
||||
/// Writes a closure to the output.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&mut self` - A mutable reference to the `FileBuilder` struct.
|
||||
/// * `output` - An object implementing Write trait where the closure will be written.
|
||||
/// * `eof` - A boolean value, if true, indicates that this is the last element to write to the output.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<()>` - A generic Result type indicating the operations outcome.
|
||||
fn write_closure(&mut self, output: &mut dyn Write, eof: bool) -> Result<()>;
|
||||
}
|
||||
|
@ -1,27 +1,27 @@
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
io::{Result, Write},
|
||||
};
|
||||
use super::FileBuilder;
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
use super::FileBuilder;
|
||||
use std::collections::BTreeMap;
|
||||
use std::io::{Result, Write};
|
||||
|
||||
/// Represents an offset value in JSON format.
|
||||
/// Represents a JSON offset value with an optional comment.
|
||||
#[derive(Debug, PartialEq, Default, Serialize)]
|
||||
struct JsonOffsetValue {
|
||||
value: usize,
|
||||
comment: Option<String>,
|
||||
}
|
||||
|
||||
/// Represents a module in JSON format.
|
||||
/// Represents a JSON module, which contains data in the form of a `BTreeMap` of string keys and
|
||||
/// `JsonOffsetValue` values, as well as an optional comment.
|
||||
#[derive(Debug, PartialEq, Default, Serialize)]
|
||||
struct JsonModule {
|
||||
data: BTreeMap<String, JsonOffsetValue>,
|
||||
comment: Option<String>,
|
||||
}
|
||||
|
||||
/// Represents a JSON file builder.
|
||||
/// A structure representing a builder for JSON files.
|
||||
/// The builder implements the `FileBuilder` trait.
|
||||
#[derive(Debug, PartialEq, Default)]
|
||||
pub struct JsonFileBuilder {
|
||||
data: BTreeMap<String, JsonModule>,
|
||||
@ -43,8 +43,8 @@ impl FileBuilder for JsonFileBuilder {
|
||||
name: &str,
|
||||
comment: Option<&str>,
|
||||
) -> Result<()> {
|
||||
self.current_namespace = name.to_string();
|
||||
self.data.entry(name.to_string()).or_default().comment = comment.map(str::to_string);
|
||||
self.current_namespace = name.to_string();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -55,6 +55,7 @@ impl FileBuilder for JsonFileBuilder {
|
||||
name: &str,
|
||||
value: usize,
|
||||
comment: Option<&str>,
|
||||
_indentation: Option<usize>,
|
||||
) -> Result<()> {
|
||||
self.data
|
||||
.entry(self.current_namespace.clone())
|
||||
@ -63,7 +64,7 @@ impl FileBuilder for JsonFileBuilder {
|
||||
.insert(
|
||||
name.to_string(),
|
||||
JsonOffsetValue {
|
||||
value: value,
|
||||
value,
|
||||
comment: comment.map(str::to_string),
|
||||
},
|
||||
);
|
||||
@ -75,7 +76,7 @@ impl FileBuilder for JsonFileBuilder {
|
||||
if eof {
|
||||
write!(output, "{}", serde_json::to_string_pretty(&self.data)?)?;
|
||||
|
||||
self.data = BTreeMap::new();
|
||||
self.data.clear();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -1,5 +1,3 @@
|
||||
pub use std::io::{Result, Write};
|
||||
|
||||
pub use cpp_file_builder::CppFileBuilder;
|
||||
pub use csharp_file_builder::CSharpFileBuilder;
|
||||
pub use file_builder::FileBuilder;
|
||||
@ -7,6 +5,8 @@ pub use json_file_builder::JsonFileBuilder;
|
||||
pub use python_file_builder::PythonFileBuilder;
|
||||
pub use rust_file_builder::RustFileBuilder;
|
||||
|
||||
pub use std::io::{Result, Write};
|
||||
|
||||
pub mod cpp_file_builder;
|
||||
pub mod csharp_file_builder;
|
||||
pub mod file_builder;
|
||||
@ -14,13 +14,23 @@ pub mod json_file_builder;
|
||||
pub mod python_file_builder;
|
||||
pub mod rust_file_builder;
|
||||
|
||||
/// Represents a file builder enum.
|
||||
/// `FileBuilder` is an enum that defines different kinds of file builders.
|
||||
/// Each variant corresponds to a builder for a particular type of file.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum FileBuilderEnum {
|
||||
/// Represents a builder for C++ header files.
|
||||
CppFileBuilder(CppFileBuilder),
|
||||
|
||||
/// Represents a builder for C# files.
|
||||
CSharpFileBuilder(CSharpFileBuilder),
|
||||
|
||||
/// Represents a builder for JSON files.
|
||||
JsonFileBuilder(JsonFileBuilder),
|
||||
|
||||
/// Represents a builder for Python files.
|
||||
PythonFileBuilder(PythonFileBuilder),
|
||||
|
||||
/// Represents a builder for Rust files.
|
||||
RustFileBuilder(RustFileBuilder),
|
||||
}
|
||||
|
||||
@ -48,8 +58,10 @@ impl FileBuilder for FileBuilderEnum {
|
||||
name: &str,
|
||||
value: usize,
|
||||
comment: Option<&str>,
|
||||
indentation: Option<usize>,
|
||||
) -> Result<()> {
|
||||
self.as_mut().write_variable(output, name, value, comment)
|
||||
self.as_mut()
|
||||
.write_variable(output, name, value, comment, indentation)
|
||||
}
|
||||
|
||||
fn write_closure(&mut self, output: &mut dyn Write, eof: bool) -> Result<()> {
|
||||
|
@ -1,8 +1,9 @@
|
||||
use std::io::{Result, Write};
|
||||
|
||||
use super::FileBuilder;
|
||||
|
||||
/// Represents a Python file builder.
|
||||
use std::io::{Result, Write};
|
||||
|
||||
/// A structure representing a builder for Python files.
|
||||
/// The builder implements the `FileBuilder` trait.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct PythonFileBuilder;
|
||||
|
||||
@ -21,13 +22,9 @@ impl FileBuilder for PythonFileBuilder {
|
||||
name: &str,
|
||||
comment: Option<&str>,
|
||||
) -> Result<()> {
|
||||
if let Some(comment) = comment {
|
||||
write!(output, "class {}: # {}\n", name, comment)?;
|
||||
} else {
|
||||
write!(output, "class {}:\n", name)?;
|
||||
}
|
||||
let comment = comment.map_or(String::new(), |c| format!("# {}", c));
|
||||
|
||||
Ok(())
|
||||
write!(output, "class {}: {}\n", name, comment)
|
||||
}
|
||||
|
||||
fn write_variable(
|
||||
@ -36,11 +33,17 @@ impl FileBuilder for PythonFileBuilder {
|
||||
name: &str,
|
||||
value: usize,
|
||||
comment: Option<&str>,
|
||||
indentation: Option<usize>,
|
||||
) -> Result<()> {
|
||||
match comment {
|
||||
Some(comment) => write!(output, " {} = {:#X} # {}\n", name, value, comment),
|
||||
None => write!(output, " {} = {:#X}\n", name, value),
|
||||
}
|
||||
let indentation = " ".repeat(indentation.unwrap_or(4));
|
||||
|
||||
let comment = comment.map_or(String::new(), |c| format!("# {}", c));
|
||||
|
||||
write!(
|
||||
output,
|
||||
"{}{} = {:#X} {}\n",
|
||||
indentation, name, value, comment
|
||||
)
|
||||
}
|
||||
|
||||
fn write_closure(&mut self, output: &mut dyn Write, eof: bool) -> Result<()> {
|
||||
|
@ -1,8 +1,9 @@
|
||||
use std::io::{Result, Write};
|
||||
|
||||
use super::FileBuilder;
|
||||
|
||||
/// Represents a Rust file builder.
|
||||
use std::io::{Result, Write};
|
||||
|
||||
/// A structure representing a builder for Rust files.
|
||||
/// The builder implements the `FileBuilder` trait.
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct RustFileBuilder;
|
||||
|
||||
@ -15,9 +16,7 @@ impl FileBuilder for RustFileBuilder {
|
||||
write!(
|
||||
output,
|
||||
"#![allow(non_snake_case, non_upper_case_globals)]\n\n"
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
)
|
||||
}
|
||||
|
||||
fn write_namespace(
|
||||
@ -26,13 +25,9 @@ impl FileBuilder for RustFileBuilder {
|
||||
name: &str,
|
||||
comment: Option<&str>,
|
||||
) -> Result<()> {
|
||||
if let Some(comment) = comment {
|
||||
write!(output, "pub mod {} {{ // {}\n", name, comment)?;
|
||||
} else {
|
||||
write!(output, "pub mod {} {{\n", name)?;
|
||||
}
|
||||
let comment = comment.map_or(String::new(), |c| format!("// {}", c));
|
||||
|
||||
Ok(())
|
||||
write!(output, "pub mod {} {{ {}\n", name, comment)
|
||||
}
|
||||
|
||||
fn write_variable(
|
||||
@ -41,20 +36,20 @@ impl FileBuilder for RustFileBuilder {
|
||||
name: &str,
|
||||
value: usize,
|
||||
comment: Option<&str>,
|
||||
indentation: Option<usize>,
|
||||
) -> Result<()> {
|
||||
match comment {
|
||||
Some(comment) => write!(
|
||||
output,
|
||||
" pub const {}: usize = {:#X}; // {}\n",
|
||||
name, value, comment
|
||||
),
|
||||
None => write!(output, " pub const {}: usize = {:#X};\n", name, value),
|
||||
}
|
||||
let indentation = " ".repeat(indentation.unwrap_or(4));
|
||||
|
||||
let comment = comment.map_or(String::new(), |c| format!("// {}", c));
|
||||
|
||||
write!(
|
||||
output,
|
||||
"{}pub const {}: usize = {:#X}; {}\n",
|
||||
indentation, name, value, comment
|
||||
)
|
||||
}
|
||||
|
||||
fn write_closure(&mut self, output: &mut dyn Write, eof: bool) -> Result<()> {
|
||||
write!(output, "{}", if eof { "}" } else { "}\n\n" })?;
|
||||
|
||||
Ok(())
|
||||
write!(output, "{}", if eof { "}" } else { "}\n\n" })
|
||||
}
|
||||
}
|
||||
|
@ -1,40 +1,71 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Represents an operation that can be performed on a memory address.
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
pub enum Operation {
|
||||
Add {
|
||||
value: usize,
|
||||
},
|
||||
/// Represents an "add" operation with a given value.
|
||||
///
|
||||
/// `value` is the value to add.
|
||||
Add { value: usize },
|
||||
|
||||
/// Represents a dereference operation with optional parameters for the number of times to dereference
|
||||
/// and the size of the resulting value.
|
||||
///
|
||||
/// `times` is the number of times to dereference the address. If `None`, the number of times will be `1`.
|
||||
/// `size` is the size of the resulting value. If `None`, the size will be `8`.
|
||||
Dereference {
|
||||
times: Option<usize>,
|
||||
size: Option<usize>,
|
||||
},
|
||||
|
||||
/// Represents a jump instruction with an optional offset and length.
|
||||
///
|
||||
/// `offset` is the offset of the displacement value. If `None`, the offset will be `0x1`.
|
||||
/// `length` is the length of the instruction. If `None`, the length will be `0x5`.
|
||||
Jmp {
|
||||
offset: Option<usize>,
|
||||
length: Option<usize>,
|
||||
},
|
||||
|
||||
/// Represents a relative instruction pointer (RIP) with an optional offset and length.
|
||||
///
|
||||
/// `offset` is the offset of the displacement value. If `None`, the offset will be `0x3`.
|
||||
/// `length` is the length of the instruction. If `None`, the length will be `0x7`.
|
||||
RipRelative {
|
||||
offset: Option<usize>,
|
||||
length: Option<usize>,
|
||||
},
|
||||
Slice {
|
||||
start: usize,
|
||||
end: usize,
|
||||
},
|
||||
Subtract {
|
||||
value: usize,
|
||||
},
|
||||
|
||||
/// Represents a slice operation with a start and end index.
|
||||
///
|
||||
/// `start` is the start index of the slice.
|
||||
/// `end` is the end index of the slice.
|
||||
Slice { start: usize, end: usize },
|
||||
|
||||
/// Represents a subtract operation with a given value.
|
||||
///
|
||||
/// `value` is the value to subtract.
|
||||
Subtract { value: usize },
|
||||
}
|
||||
|
||||
/// Represents a signature for a specific module.
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Signature {
|
||||
/// The name of the signature.
|
||||
pub name: String,
|
||||
|
||||
/// The name of the module.
|
||||
pub module: String,
|
||||
|
||||
/// The pattern of the signature.
|
||||
pub pattern: String,
|
||||
|
||||
/// The list of operations to perform on the signature.
|
||||
pub operations: Vec<Operation>,
|
||||
}
|
||||
|
||||
/// Configuration struct that holds a vector of `Signature` structs.
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Config {
|
||||
pub signatures: Vec<Signature>,
|
||||
|
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(())
|
||||
}
|
@ -1,15 +1,141 @@
|
||||
use std::fs::File;
|
||||
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 convert_case::{Case, Casing};
|
||||
use simplelog::{debug, error, info};
|
||||
|
||||
use crate::builder::FileBuilderEnum;
|
||||
use crate::config::{Config, Operation::*};
|
||||
use crate::dumpers::Entry;
|
||||
use crate::remote::Process;
|
||||
use std::fs::File;
|
||||
|
||||
use super::{generate_files, Entries};
|
||||
// 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 {
|
||||
@ -22,7 +148,10 @@ mod tests {
|
||||
fn build_number() -> Result<()> {
|
||||
let process = Process::new("cs2.exe")?;
|
||||
|
||||
let engine_base = process.get_module_by_name("engine2.dll")?.base_address();
|
||||
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)?;
|
||||
|
||||
@ -72,7 +201,10 @@ mod tests {
|
||||
|
||||
let process = Process::new("cs2.exe")?;
|
||||
|
||||
let client_base = process.get_module_by_name("client.dll")?.base_address();
|
||||
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)?;
|
||||
|
||||
@ -91,7 +223,10 @@ mod tests {
|
||||
fn local_player() -> Result<()> {
|
||||
let process = Process::new("cs2.exe")?;
|
||||
|
||||
let client_base = process.get_module_by_name("client.dll")?.base_address();
|
||||
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)?;
|
||||
|
||||
@ -106,7 +241,10 @@ mod tests {
|
||||
fn window_size() -> Result<()> {
|
||||
let process = Process::new("cs2.exe")?;
|
||||
|
||||
let engine_base = process.get_module_by_name("engine2.dll")?.base_address();
|
||||
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)?;
|
||||
@ -116,96 +254,3 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dump_offsets(builders: &mut Vec<FileBuilderEnum>, process: &Process) -> Result<()> {
|
||||
let file = File::open("config.json")?;
|
||||
|
||||
let config: Config = serde_json::from_reader(file)?;
|
||||
|
||||
let mut entries = Entries::new();
|
||||
|
||||
log::info!("Dumping offsets...");
|
||||
|
||||
for signature in config.signatures {
|
||||
log::info!("Searching for {}...", signature.name);
|
||||
|
||||
let module = process.get_module_by_name(&signature.module)?;
|
||||
|
||||
let mut addr = match process.find_pattern(&signature.module, &signature.pattern) {
|
||||
Ok(a) => a,
|
||||
Err(_) => {
|
||||
log::error!("Failed to find pattern for {}.", signature.name);
|
||||
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
for op in signature.operations {
|
||||
match op {
|
||||
Add { value } => addr += value,
|
||||
Dereference { times, size } => {
|
||||
let times = times.unwrap_or(1);
|
||||
let size = size.unwrap_or(8);
|
||||
|
||||
for _ in 0..times {
|
||||
process.read_memory_raw(addr, &mut addr.0 as *mut _ as *mut _, size)?;
|
||||
}
|
||||
}
|
||||
Jmp { offset, length } => addr = process.resolve_jmp(addr, offset, length)?.into(),
|
||||
RipRelative { offset, length } => {
|
||||
addr = process.resolve_rip(addr, offset, length)?.into()
|
||||
}
|
||||
Slice { start, end } => {
|
||||
let mut result: usize = 0;
|
||||
|
||||
process.read_memory_raw(
|
||||
addr.add(start),
|
||||
&mut result as *mut _ as *mut _,
|
||||
end - start,
|
||||
)?;
|
||||
|
||||
addr = result.into();
|
||||
}
|
||||
Subtract { value } => addr -= value,
|
||||
}
|
||||
}
|
||||
|
||||
let (name, value) = if addr < module.base_address() {
|
||||
log::debug!(" └─ {} @ {:#X}", signature.name, addr.0);
|
||||
|
||||
(signature.name, addr.0)
|
||||
} else {
|
||||
log::debug!(
|
||||
" └─ {} @ {:#X} ({} + {:#X})",
|
||||
signature.name,
|
||||
addr,
|
||||
signature.module,
|
||||
addr.sub(module.base_address().0)
|
||||
);
|
||||
|
||||
(signature.name, addr.sub(module.base_address().0).0)
|
||||
};
|
||||
|
||||
let container = entries
|
||||
.entry(
|
||||
signature
|
||||
.module
|
||||
.replace(".", "_")
|
||||
.to_case(Case::Snake)
|
||||
.to_case(Case::Pascal),
|
||||
)
|
||||
.or_default();
|
||||
|
||||
container.comment = Some(signature.module);
|
||||
|
||||
container.data.push(Entry {
|
||||
name,
|
||||
value,
|
||||
comment: None,
|
||||
});
|
||||
}
|
||||
|
||||
generate_files(builders, &entries, "offsets")?;
|
||||
|
||||
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(())
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
use anyhow::Result;
|
||||
|
||||
use convert_case::{Case, Casing};
|
||||
|
||||
use crate::builder::FileBuilderEnum;
|
||||
use crate::dumpers::Entry;
|
||||
use crate::remote::Process;
|
||||
use crate::sdk::InterfaceReg;
|
||||
|
||||
use super::{generate_files, Entries};
|
||||
|
||||
pub fn dump_interfaces(builders: &mut Vec<FileBuilderEnum>, process: &Process) -> Result<()> {
|
||||
let mut entries = Entries::new();
|
||||
|
||||
for module_name in process
|
||||
.get_loaded_modules()?
|
||||
.into_iter()
|
||||
.filter(|module_name| *module_name != "crashhandler64.dll")
|
||||
{
|
||||
let module = process.get_module_by_name(&module_name)?;
|
||||
|
||||
if let Some(create_interface_export) = module.export("CreateInterface") {
|
||||
log::info!("Dumping interfaces in {}...", module_name);
|
||||
|
||||
let create_interface_addr =
|
||||
process.resolve_rip(create_interface_export.va.into(), None, None)?;
|
||||
|
||||
if let Ok(mut interface_reg) =
|
||||
process.read_memory::<*mut InterfaceReg>(create_interface_addr)
|
||||
{
|
||||
while !interface_reg.is_null() {
|
||||
let ptr = unsafe { (*interface_reg).pointer(process) }?;
|
||||
let name = unsafe { (*interface_reg).name(process) }?;
|
||||
|
||||
log::debug!(
|
||||
" └─ {} @ {:#X} ({} + {:#X})",
|
||||
name,
|
||||
ptr,
|
||||
module_name,
|
||||
ptr - module.base_address()
|
||||
);
|
||||
|
||||
let container = entries
|
||||
.entry(
|
||||
module_name
|
||||
.replace(".", "_")
|
||||
.to_case(Case::Snake)
|
||||
.to_case(Case::Pascal),
|
||||
)
|
||||
.or_default();
|
||||
|
||||
container.comment = Some(module_name.clone());
|
||||
|
||||
container.data.push(Entry {
|
||||
name: name.clone(),
|
||||
value: (ptr - module.base_address()).into(),
|
||||
comment: None,
|
||||
});
|
||||
|
||||
interface_reg = unsafe { (*interface_reg).next(process) }?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
generate_files(builders, &entries, "interfaces")?;
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use chrono::Utc;
|
||||
|
||||
use crate::builder::{FileBuilder, FileBuilderEnum};
|
||||
|
||||
pub use interfaces::dump_interfaces;
|
||||
pub use offsets::dump_offsets;
|
||||
pub use schemas::dump_schemas;
|
||||
|
||||
pub mod interfaces;
|
||||
pub mod offsets;
|
||||
pub mod schemas;
|
||||
|
||||
/// A single entry.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Entry {
|
||||
pub name: String,
|
||||
pub value: usize,
|
||||
pub comment: Option<String>,
|
||||
}
|
||||
|
||||
/// A container for entries.
|
||||
#[derive(Default)]
|
||||
pub struct EntriesContainer {
|
||||
pub data: Vec<Entry>,
|
||||
pub comment: Option<String>,
|
||||
}
|
||||
|
||||
/// A map of entries.
|
||||
pub type Entries = BTreeMap<String, EntriesContainer>;
|
||||
|
||||
/// Generate a file with the given builder.
|
||||
pub fn generate_file(
|
||||
builder: &mut FileBuilderEnum,
|
||||
entries: &Entries,
|
||||
file_name: &str,
|
||||
) -> Result<()> {
|
||||
if entries.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let file_path = format!("generated/{}.{}", 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(),
|
||||
)
|
||||
})?;
|
||||
|
||||
builder.write_closure(&mut file, i == len - 1)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generate files with the given builders.
|
||||
pub fn generate_files(
|
||||
builders: &mut [FileBuilderEnum],
|
||||
entries: &Entries,
|
||||
file_name: &str,
|
||||
) -> Result<()> {
|
||||
builders
|
||||
.iter_mut()
|
||||
.try_for_each(|builder| generate_file(builder, entries, file_name))
|
||||
}
|
||||
|
||||
/// Writes the banner to the given file.
|
||||
fn write_banner_to_file(file: &mut File, file_ext: &str) -> Result<()> {
|
||||
const REPO_URL: &str = "https://github.com/a2x/cs2-dumper";
|
||||
|
||||
let now_utc = Utc::now();
|
||||
|
||||
let banner = match file_ext {
|
||||
"json" => None,
|
||||
"py" => Some(format!(
|
||||
"'''\nCreated using {}\n{}\n'''\n\n",
|
||||
REPO_URL, now_utc
|
||||
)),
|
||||
_ => Some(format!(
|
||||
"/*\n * Created using {}\n * {}\n */\n\n",
|
||||
REPO_URL, now_utc
|
||||
)),
|
||||
};
|
||||
|
||||
if let Some(banner) = banner {
|
||||
write!(file, "{}", banner)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::builder::FileBuilderEnum;
|
||||
use crate::dumpers::Entry;
|
||||
use crate::remote::Process;
|
||||
use crate::sdk::SchemaSystem;
|
||||
|
||||
use super::{generate_files, Entries};
|
||||
|
||||
pub fn dump_schemas(builders: &mut Vec<FileBuilderEnum>, process: &Process) -> Result<()> {
|
||||
let schema_system = SchemaSystem::new(&process)?;
|
||||
|
||||
for type_scope in schema_system.type_scopes()? {
|
||||
let module_name = type_scope.module_name()?;
|
||||
|
||||
log::info!("Generating files for {}...", module_name);
|
||||
|
||||
let mut entries = Entries::new();
|
||||
|
||||
for class in type_scope.classes()? {
|
||||
log::debug!(" {}", class.name());
|
||||
|
||||
let container = entries.entry(class.name().replace("::", "_")).or_default();
|
||||
|
||||
container.comment = class.parent()?.map(|p| p.name().to_string());
|
||||
|
||||
for field in class.fields()? {
|
||||
let field_name = field.name()?;
|
||||
let field_offset = field.offset()?;
|
||||
let field_type_name = field.r#type()?.name()?;
|
||||
|
||||
log::debug!(
|
||||
" └─ {} = {:#X} // {}",
|
||||
field_name,
|
||||
field_offset,
|
||||
field_type_name
|
||||
);
|
||||
|
||||
container.data.push(Entry {
|
||||
name: field_name,
|
||||
value: field_offset as usize,
|
||||
comment: Some(field_type_name),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
generate_files(builders, &entries, &module_name)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
79
src/main.rs
79
src/main.rs
@ -1,40 +1,58 @@
|
||||
#![allow(dead_code)]
|
||||
#![feature(offset_of)]
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use builder::*;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use dumper::{dump_interfaces, dump_offsets, dump_schemas};
|
||||
|
||||
use log::LevelFilter;
|
||||
|
||||
use simplelog::{info, ColorChoice, ConfigBuilder, TermLogger, TerminalMode};
|
||||
|
||||
use std::fs;
|
||||
use std::time::Instant;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
|
||||
use builder::*;
|
||||
use dumpers::*;
|
||||
use remote::Process;
|
||||
use util::Process;
|
||||
|
||||
mod builder;
|
||||
mod config;
|
||||
mod dumpers;
|
||||
mod mem;
|
||||
mod remote;
|
||||
mod dumper;
|
||||
mod sdk;
|
||||
mod util;
|
||||
|
||||
/// Command line arguments for the program.
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
#[command(name = "cs2-dumper")]
|
||||
#[command(author = "a2x")]
|
||||
#[command(version = "1.1.4")]
|
||||
struct Args {
|
||||
/// Dump interfaces.
|
||||
#[arg(short, long)]
|
||||
interfaces: bool,
|
||||
|
||||
/// Dump offsets.
|
||||
#[arg(short, long)]
|
||||
offsets: bool,
|
||||
|
||||
/// Dump schemas.
|
||||
#[arg(short, long)]
|
||||
schemas: bool,
|
||||
|
||||
/// Enable verbose output.
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
|
||||
/// Output folder.
|
||||
#[arg(short, long, default_value = "generated")]
|
||||
output: String,
|
||||
|
||||
/// Indentation level.
|
||||
#[arg(long, default_value = "4")]
|
||||
indent: usize,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
@ -43,25 +61,27 @@ fn main() -> Result<()> {
|
||||
offsets,
|
||||
schemas,
|
||||
verbose,
|
||||
output,
|
||||
indent,
|
||||
} = Args::parse();
|
||||
|
||||
let log_level = if verbose {
|
||||
log::LevelFilter::Debug
|
||||
LevelFilter::Debug
|
||||
} else {
|
||||
log::LevelFilter::Info
|
||||
LevelFilter::Info
|
||||
};
|
||||
|
||||
SimpleLogger::new()
|
||||
.with_level(log_level)
|
||||
.without_timestamps()
|
||||
.init()
|
||||
.unwrap();
|
||||
let config = ConfigBuilder::new().add_filter_ignore_str("goblin").build();
|
||||
|
||||
let start_time = Instant::now();
|
||||
TermLogger::init(log_level, config, TerminalMode::Mixed, ColorChoice::Auto)?;
|
||||
|
||||
let process = Process::new("cs2.exe")?;
|
||||
let now = Instant::now();
|
||||
|
||||
fs::create_dir_all("generated")?;
|
||||
fs::create_dir_all(&output)?;
|
||||
|
||||
let mut process = Process::new("cs2.exe")?;
|
||||
|
||||
process.initialize()?;
|
||||
|
||||
let mut builders: Vec<FileBuilderEnum> = vec![
|
||||
FileBuilderEnum::CppFileBuilder(CppFileBuilder),
|
||||
@ -74,20 +94,21 @@ fn main() -> Result<()> {
|
||||
let all = !(interfaces || offsets || schemas);
|
||||
|
||||
if schemas || all {
|
||||
dump_schemas(&mut builders, &process)?;
|
||||
dump_schemas(&mut process, &mut builders, &output, indent)?;
|
||||
}
|
||||
|
||||
if interfaces || all {
|
||||
dump_interfaces(&mut builders, &process)?;
|
||||
dump_interfaces(&mut process, &mut builders, &output, indent)?;
|
||||
}
|
||||
|
||||
if offsets || all {
|
||||
dump_offsets(&mut builders, &process)?;
|
||||
dump_offsets(&mut process, &mut builders, &output, indent)?;
|
||||
}
|
||||
|
||||
let duration = start_time.elapsed();
|
||||
|
||||
log::info!("Done! Time elapsed: {:?}", duration);
|
||||
info!(
|
||||
"<on-green>Done!</> <green>Time elapsed: {:?}</>",
|
||||
now.elapsed()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,114 +0,0 @@
|
||||
use std::fmt::{LowerHex, UpperHex};
|
||||
use std::ops::{Add, AddAssign, Sub, SubAssign};
|
||||
|
||||
/// Represents a memory address.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd)]
|
||||
#[repr(transparent)]
|
||||
pub struct Address(pub usize);
|
||||
|
||||
impl Address {
|
||||
/// Add the given value to the address.
|
||||
pub fn add(&self, value: usize) -> Self {
|
||||
Self(self.0 + value)
|
||||
}
|
||||
|
||||
/// Returns `true` if the address is zero.
|
||||
pub fn is_zero(&self) -> bool {
|
||||
self.0 == 0
|
||||
}
|
||||
|
||||
/// Subtract the given value from the address.
|
||||
pub fn sub(&self, value: usize) -> Self {
|
||||
Self(self.0 - value)
|
||||
}
|
||||
|
||||
/// Get the address as a pointer.
|
||||
pub fn as_ptr<T>(&self) -> *const T {
|
||||
self.0 as *const T
|
||||
}
|
||||
|
||||
/// Get the address as a mutable pointer.
|
||||
pub fn as_mut_ptr<T>(&self) -> *mut T {
|
||||
self.0 as *mut T
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for Address {
|
||||
fn from(value: usize) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Address> for usize {
|
||||
fn from(value: Address) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<usize> for Address {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: usize) -> Self::Output {
|
||||
Self(self.0 + rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Address> for Address {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Address) -> Self::Output {
|
||||
Self(self.0 + rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<usize> for Address {
|
||||
fn add_assign(&mut self, rhs: usize) {
|
||||
self.0 += rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<Address> for Address {
|
||||
fn add_assign(&mut self, rhs: Address) {
|
||||
self.0 += rhs.0;
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<usize> for Address {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, rhs: usize) -> Self::Output {
|
||||
Self(self.0 - rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Address> for Address {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, rhs: Address) -> Self::Output {
|
||||
Self(self.0 - rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign<usize> for Address {
|
||||
fn sub_assign(&mut self, rhs: usize) {
|
||||
self.0 -= rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign<Address> for Address {
|
||||
fn sub_assign(&mut self, rhs: Address) {
|
||||
self.0 -= rhs.0;
|
||||
}
|
||||
}
|
||||
|
||||
impl UpperHex for Address {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:#X}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl LowerHex for Address {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:#x}", self.0)
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
pub use address::Address;
|
||||
|
||||
pub mod address;
|
@ -1,254 +0,0 @@
|
||||
use std::ffi::CStr;
|
||||
use std::mem;
|
||||
use std::slice;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
use windows::Win32::System::Diagnostics::Debug::*;
|
||||
use windows::Win32::System::SystemServices::*;
|
||||
|
||||
use crate::mem::Address;
|
||||
|
||||
use super::Process;
|
||||
|
||||
/// Represents an export in a module.
|
||||
#[derive(Debug)]
|
||||
pub struct Export {
|
||||
/// Name of the export.
|
||||
pub name: String,
|
||||
|
||||
/// Address of the export.
|
||||
pub va: usize,
|
||||
}
|
||||
|
||||
/// Represents a section in a module.
|
||||
#[derive(Debug)]
|
||||
pub struct Section {
|
||||
/// Name of the section.
|
||||
pub name: String,
|
||||
|
||||
/// Base address of the section.
|
||||
pub start_va: usize,
|
||||
|
||||
/// End address of the section.
|
||||
pub end_va: usize,
|
||||
|
||||
/// Size of the section.
|
||||
pub size: usize,
|
||||
}
|
||||
|
||||
/// Represents a module in a process.
|
||||
pub struct Module<'a> {
|
||||
/// Base address of the module.
|
||||
base_addr: Address,
|
||||
|
||||
/// DOS header.
|
||||
dos_hdr: &'a IMAGE_DOS_HEADER,
|
||||
|
||||
/// NT header.
|
||||
nt_hdr: &'a IMAGE_NT_HEADERS64,
|
||||
|
||||
/// Size of the module.
|
||||
size: u32,
|
||||
|
||||
/// List of exports.
|
||||
exports: Vec<Export>,
|
||||
|
||||
/// List of sections.
|
||||
sections: Vec<Section>,
|
||||
}
|
||||
|
||||
impl<'a> Module<'a> {
|
||||
pub fn new(process: &'a Process, base_addr: Address) -> Result<Self> {
|
||||
let mut buf: [u8; 0x1000] = [0; 0x1000];
|
||||
|
||||
process.read_memory_raw(base_addr, buf.as_mut_ptr() as *mut _, buf.len())?;
|
||||
|
||||
if buf.len() < mem::size_of::<IMAGE_DOS_HEADER>() {
|
||||
bail!(
|
||||
"Buffer size mismatch. Expected {} bytes, got {} bytes",
|
||||
mem::size_of::<IMAGE_DOS_HEADER>(),
|
||||
buf.len()
|
||||
);
|
||||
}
|
||||
|
||||
let dos_hdr = unsafe { &*(buf.as_ptr() as *const IMAGE_DOS_HEADER) };
|
||||
|
||||
if dos_hdr.e_magic != IMAGE_DOS_SIGNATURE {
|
||||
bail!(
|
||||
"Invalid DOS signature. Expected 0x{:X}, got 0x{:X}",
|
||||
IMAGE_DOS_SIGNATURE,
|
||||
dos_hdr.e_magic
|
||||
);
|
||||
}
|
||||
|
||||
let nt_hdr = unsafe {
|
||||
&*(buf.as_ptr().offset(dos_hdr.e_lfanew as isize) as *const IMAGE_NT_HEADERS64)
|
||||
};
|
||||
|
||||
if nt_hdr.Signature != IMAGE_NT_SIGNATURE {
|
||||
bail!(
|
||||
"Invalid NT signature. Expected 0x{:X}, got 0x{:X}",
|
||||
IMAGE_NT_SIGNATURE,
|
||||
nt_hdr.Signature
|
||||
);
|
||||
}
|
||||
|
||||
let size = nt_hdr.OptionalHeader.SizeOfImage;
|
||||
|
||||
let exports = unsafe { Self::parse_exports(process, base_addr, size, nt_hdr)? };
|
||||
let sections = unsafe { Self::parse_sections(base_addr, nt_hdr) };
|
||||
|
||||
Ok(Self {
|
||||
base_addr,
|
||||
dos_hdr,
|
||||
nt_hdr,
|
||||
size,
|
||||
exports,
|
||||
sections,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the base address of the module.
|
||||
#[inline]
|
||||
pub fn base_address(&self) -> Address {
|
||||
self.base_addr
|
||||
}
|
||||
|
||||
/// Returns the exports of the module.
|
||||
#[inline]
|
||||
pub fn exports(&self) -> &Vec<Export> {
|
||||
&self.exports
|
||||
}
|
||||
|
||||
/// Returns the sections of the module.
|
||||
#[inline]
|
||||
pub fn sections(&self) -> &Vec<Section> {
|
||||
&self.sections
|
||||
}
|
||||
|
||||
/// Returns the export with the given name.
|
||||
#[inline]
|
||||
pub fn export(&self, name: &str) -> Option<&Export> {
|
||||
self.exports.iter().find(|export| export.name == name)
|
||||
}
|
||||
|
||||
/// Returns the section with the given name.
|
||||
#[inline]
|
||||
pub fn section(&self, name: &str) -> Option<&Section> {
|
||||
self.sections.iter().find(|section| section.name == name)
|
||||
}
|
||||
|
||||
/// Returns the size of the module.
|
||||
#[inline]
|
||||
pub fn size(&self) -> u32 {
|
||||
self.size
|
||||
}
|
||||
|
||||
/// Parses the exports of the module.
|
||||
unsafe fn parse_exports(
|
||||
process: &Process,
|
||||
base_addr: Address,
|
||||
size: u32,
|
||||
nt_hdr: &IMAGE_NT_HEADERS64,
|
||||
) -> Result<Vec<Export>> {
|
||||
let export_data_dir =
|
||||
nt_hdr.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT.0 as usize];
|
||||
|
||||
let export_dir_start = base_addr + export_data_dir.VirtualAddress as usize;
|
||||
let export_dir_end = export_dir_start + export_data_dir.Size as usize;
|
||||
|
||||
let mut buf: Vec<u8> = vec![0; export_data_dir.Size as usize];
|
||||
|
||||
process.read_memory_raw(export_dir_start, buf.as_mut_ptr() as *mut _, buf.len())?;
|
||||
|
||||
if buf.len() < mem::size_of::<IMAGE_EXPORT_DIRECTORY>() {
|
||||
bail!(
|
||||
"Buffer size mismatch. Expected {} bytes, got {} bytes",
|
||||
mem::size_of::<IMAGE_EXPORT_DIRECTORY>(),
|
||||
buf.len()
|
||||
);
|
||||
}
|
||||
|
||||
let export_dir = &*(buf.as_ptr() as *const IMAGE_EXPORT_DIRECTORY);
|
||||
|
||||
let delta = export_dir as *const _ as usize - export_data_dir.VirtualAddress as usize;
|
||||
|
||||
let name_table = (delta + export_dir.AddressOfNames as usize) as *const u32;
|
||||
let ordinal_table = (delta + export_dir.AddressOfNameOrdinals as usize) as *const u16;
|
||||
let function_table = (delta + export_dir.AddressOfFunctions as usize) as *const u32;
|
||||
|
||||
let mut exports: Vec<Export> = Vec::with_capacity(export_dir.NumberOfNames as usize);
|
||||
|
||||
for i in 0..export_dir.NumberOfNames {
|
||||
let target_va = ordinal_table as usize + i as usize * mem::size_of::<u16>();
|
||||
|
||||
if target_va > base_addr.add(size as usize).0 || target_va < ordinal_table as usize {
|
||||
continue;
|
||||
}
|
||||
|
||||
let func_ordinal = *ordinal_table.offset(i as isize);
|
||||
|
||||
if func_ordinal as usize > export_dir.NumberOfFunctions as usize {
|
||||
continue;
|
||||
}
|
||||
|
||||
let func_va = base_addr.add(*function_table.offset(func_ordinal as isize) as usize);
|
||||
|
||||
// Skip forwarded exports.
|
||||
if func_va >= export_dir_start && func_va <= export_dir_end {
|
||||
continue;
|
||||
}
|
||||
|
||||
let name_ptr = delta.wrapping_add(*name_table.offset(i as isize) as usize) as *const i8;
|
||||
|
||||
if name_ptr.is_null() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let name = CStr::from_ptr(name_ptr).to_string_lossy().into_owned();
|
||||
|
||||
exports.push(Export {
|
||||
name,
|
||||
va: func_va.0,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(exports)
|
||||
}
|
||||
|
||||
/// Parses the sections of the module.
|
||||
unsafe fn parse_sections(base_addr: Address, nt_hdr: &IMAGE_NT_HEADERS64) -> Vec<Section> {
|
||||
let optional_hdr = &nt_hdr.OptionalHeader as *const _ as *const u8;
|
||||
|
||||
let section_hdr = optional_hdr.offset(nt_hdr.FileHeader.SizeOfOptionalHeader as isize)
|
||||
as *const IMAGE_SECTION_HEADER;
|
||||
|
||||
let sections_raw =
|
||||
slice::from_raw_parts(section_hdr, nt_hdr.FileHeader.NumberOfSections as usize);
|
||||
|
||||
sections_raw
|
||||
.iter()
|
||||
.map(|section| {
|
||||
let name = CStr::from_ptr(section.Name.as_ptr() as *const _)
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
|
||||
let start_rva = section.VirtualAddress as usize;
|
||||
let end_rva = start_rva + section.Misc.VirtualSize as usize;
|
||||
|
||||
let start_va = base_addr + start_rva;
|
||||
let end_va = base_addr + end_rva;
|
||||
|
||||
let size = section.SizeOfRawData as usize;
|
||||
|
||||
Section {
|
||||
name,
|
||||
start_va: start_va.0,
|
||||
end_va: end_va.0,
|
||||
size,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
@ -1,285 +0,0 @@
|
||||
use std::ffi::{c_void, CStr};
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
use windows::Win32::Foundation::*;
|
||||
use windows::Win32::System::Diagnostics::Debug::*;
|
||||
use windows::Win32::System::Diagnostics::ToolHelp::*;
|
||||
use windows::Win32::System::Threading::*;
|
||||
|
||||
use crate::mem::Address;
|
||||
|
||||
use super::Module;
|
||||
|
||||
/// Represents a Win32 process.
|
||||
#[derive(Debug)]
|
||||
pub struct Process {
|
||||
process_id: u32,
|
||||
process_handle: HANDLE,
|
||||
}
|
||||
|
||||
impl Process {
|
||||
pub fn new(process_name: &str) -> Result<Self> {
|
||||
let process_id = Self::get_process_id_by_name(process_name)?;
|
||||
|
||||
let process_handle = unsafe { OpenProcess(PROCESS_ALL_ACCESS, false, process_id) }?;
|
||||
|
||||
Ok(Self {
|
||||
process_id,
|
||||
process_handle,
|
||||
})
|
||||
}
|
||||
|
||||
/// Finds an sequence of bytes in memory.
|
||||
pub fn find_pattern(&self, module_name: &str, pattern: &str) -> Result<Address> {
|
||||
let module = self.get_module_by_name(module_name)?;
|
||||
|
||||
let mut module_data: Vec<u8> = vec![0; module.size() as usize];
|
||||
|
||||
self.read_memory_raw(
|
||||
module.base_address(),
|
||||
module_data.as_mut_ptr() as *mut _,
|
||||
module_data.len(),
|
||||
)?;
|
||||
|
||||
let pat_bytes = Self::pattern_to_bytes(pattern);
|
||||
|
||||
for i in 0..module.size() as usize - pattern.len() {
|
||||
let mut found = true;
|
||||
|
||||
for j in 0..pat_bytes.len() {
|
||||
if module_data[i + j] != pat_bytes[j] as u8 && pat_bytes[j] != -1 {
|
||||
found = false;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
return Ok(module.base_address() + i);
|
||||
}
|
||||
}
|
||||
|
||||
bail!("Pattern not found: {}", pattern)
|
||||
}
|
||||
|
||||
/// Returns a list of loaded modules.
|
||||
pub fn get_loaded_modules(&self) -> Result<Vec<String>> {
|
||||
let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, self.process_id) }?;
|
||||
|
||||
let mut entry = MODULEENTRY32 {
|
||||
dwSize: mem::size_of::<MODULEENTRY32>() as u32,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut modules = Vec::new();
|
||||
|
||||
unsafe {
|
||||
Module32First(snapshot, &mut entry)?;
|
||||
|
||||
while Module32Next(snapshot, &mut entry).is_ok() {
|
||||
let name = CStr::from_ptr(&entry.szModule as *const _ as *const _)
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
|
||||
modules.push(name);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(modules)
|
||||
}
|
||||
|
||||
/// Returns a module by name.
|
||||
pub fn get_module_by_name(&self, module_name: &str) -> Result<Module> {
|
||||
let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, self.process_id) }?;
|
||||
|
||||
let mut entry = MODULEENTRY32 {
|
||||
dwSize: mem::size_of::<MODULEENTRY32>() as u32,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
unsafe {
|
||||
Module32First(snapshot, &mut entry)?;
|
||||
|
||||
while Module32Next(snapshot, &mut entry).is_ok() {
|
||||
let name = CStr::from_ptr(&entry.szModule as *const _ as *const _)
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
|
||||
if name == module_name {
|
||||
return Module::new(self, Address::from(entry.modBaseAddr as usize));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bail!("Module not found: {}", module_name)
|
||||
}
|
||||
|
||||
/// Reads raw data from memory.
|
||||
pub fn read_memory_raw(&self, addr: Address, buf: *mut c_void, size: usize) -> Result<()> {
|
||||
unsafe {
|
||||
ReadProcessMemory(
|
||||
self.process_handle,
|
||||
addr.as_ptr(),
|
||||
buf,
|
||||
size,
|
||||
Some(ptr::null_mut()),
|
||||
)
|
||||
}
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Writes raw data to memory.
|
||||
pub fn write_memory_raw(&self, addr: Address, buf: *const c_void, size: usize) -> Result<()> {
|
||||
unsafe {
|
||||
WriteProcessMemory(
|
||||
self.process_handle,
|
||||
addr.as_ptr(),
|
||||
buf,
|
||||
size,
|
||||
Some(ptr::null_mut()),
|
||||
)
|
||||
}
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Reads a value from memory.
|
||||
pub fn read_memory<T>(&self, addr: Address) -> Result<T> {
|
||||
let mut buf: T = unsafe { mem::zeroed() };
|
||||
|
||||
self.read_memory_raw(addr, &mut buf as *const _ as *mut _, mem::size_of::<T>())?;
|
||||
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
/// Writes a value to memory.
|
||||
pub fn write_memory<T>(&self, addr: Address, val: T) -> Result<()> {
|
||||
self.write_memory_raw(addr, &val as *const _ as *const _, mem::size_of::<T>())
|
||||
}
|
||||
|
||||
/// Reads a string.
|
||||
pub fn read_string(&self, addr: Address) -> Result<String> {
|
||||
let mut buf = Vec::new();
|
||||
|
||||
for i in 0.. {
|
||||
match self.read_memory::<u8>(addr + i) {
|
||||
Ok(byte) if byte != 0 => buf.push(byte),
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(String::from_utf8(buf)?)
|
||||
}
|
||||
|
||||
/// Reads a string with a specified length.
|
||||
pub fn read_string_len(&self, addr: Address, len: usize) -> Result<String> {
|
||||
let mut buf: Vec<u8> = vec![0; len];
|
||||
|
||||
self.read_memory_raw(addr, buf.as_mut_ptr() as *mut _, len)?;
|
||||
|
||||
if let Some(end) = buf.iter().position(|&x| x == 0) {
|
||||
buf.truncate(end);
|
||||
}
|
||||
|
||||
Ok(String::from_utf8(buf)?)
|
||||
}
|
||||
|
||||
/// Resolves a JMP/CALL instruction.
|
||||
pub fn resolve_jmp(
|
||||
&self,
|
||||
addr: Address,
|
||||
offset: Option<usize>,
|
||||
len: Option<usize>,
|
||||
) -> Result<Address> {
|
||||
let disp = self.read_memory::<i32>(addr + offset.unwrap_or(0x1))?;
|
||||
|
||||
Ok(((addr.add(len.unwrap_or(0x5)).0 as isize + disp as isize) as usize).into())
|
||||
}
|
||||
|
||||
/// Resolves a RIP-relative address.
|
||||
pub fn resolve_rip(
|
||||
&self,
|
||||
addr: Address,
|
||||
offset: Option<usize>,
|
||||
len: Option<usize>,
|
||||
) -> Result<Address> {
|
||||
let disp = self.read_memory::<i32>(addr + offset.unwrap_or(0x3))?;
|
||||
|
||||
Ok(((addr.add(len.unwrap_or(0x7)).0 as isize + disp as isize) as usize).into())
|
||||
}
|
||||
|
||||
/// Returns the process ID of a process by name.
|
||||
fn get_process_id_by_name(process_name: &str) -> Result<u32> {
|
||||
let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) }?;
|
||||
|
||||
let mut entry = PROCESSENTRY32 {
|
||||
dwSize: mem::size_of::<PROCESSENTRY32>() as u32,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
unsafe {
|
||||
Process32First(snapshot, &mut entry)?;
|
||||
|
||||
while Process32Next(snapshot, &mut entry).is_ok() {
|
||||
let name = CStr::from_ptr(&entry.szExeFile as *const _ as *const _)
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
|
||||
if name == process_name {
|
||||
return Ok(entry.th32ProcessID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bail!("Process not found: {}", process_name)
|
||||
}
|
||||
|
||||
/// Converts a pattern to a list of bytes.
|
||||
fn pattern_to_bytes(pattern: &str) -> Vec<i32> {
|
||||
let mut bytes = Vec::new();
|
||||
|
||||
let chars: Vec<char> = pattern.chars().collect();
|
||||
|
||||
let mut i = 0;
|
||||
|
||||
while i < chars.len() {
|
||||
if chars[i] == ' ' {
|
||||
i += 1;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if chars[i] == '?' {
|
||||
bytes.push(-1);
|
||||
|
||||
i += 1;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if i + 1 < chars.len() {
|
||||
match i32::from_str_radix(&pattern[i..i + 2], 16) {
|
||||
Ok(v) => bytes.push(v),
|
||||
Err(_) => {}
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Process {
|
||||
/// Closes the process handle.
|
||||
fn drop(&mut self) {
|
||||
if !self.process_handle.is_invalid() {
|
||||
unsafe { CloseHandle(self.process_handle).unwrap() }
|
||||
}
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
use std::ffi::c_char;
|
||||
use std::mem::offset_of;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::mem::Address;
|
||||
use crate::remote::Process;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct InterfaceReg {
|
||||
pub create_fn: *const (), // 0x0000
|
||||
pub name: *const c_char, // 0x0008
|
||||
pub next: *mut InterfaceReg, // 0x0010
|
||||
}
|
||||
|
||||
impl InterfaceReg {
|
||||
/// Returns the pointer of the interface.
|
||||
pub fn pointer(&self, process: &Process) -> Result<Address> {
|
||||
process
|
||||
.read_memory::<usize>(
|
||||
(self as *const _ as usize + offset_of!(InterfaceReg, create_fn)).into(),
|
||||
)
|
||||
.map(|ptr| ptr.into())
|
||||
}
|
||||
|
||||
/// Returns the name of the interface.
|
||||
/// E.g. "Source2Client002"
|
||||
pub fn name(&self, process: &Process) -> Result<String> {
|
||||
let name_ptr = process.read_memory::<usize>(
|
||||
(self as *const _ as usize + offset_of!(InterfaceReg, name)).into(),
|
||||
)?;
|
||||
|
||||
process.read_string(name_ptr.into())
|
||||
}
|
||||
|
||||
/// Returns the next interface in the list.
|
||||
pub fn next(&self, process: &Process) -> Result<*mut InterfaceReg> {
|
||||
process.read_memory::<*mut InterfaceReg>(
|
||||
(self as *const _ as usize + offset_of!(InterfaceReg, next)).into(),
|
||||
)
|
||||
}
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
pub use interface::InterfaceReg;
|
||||
pub use schema_class_field_data::SchemaClassFieldData;
|
||||
pub use schema_class_info::SchemaClassInfo;
|
||||
pub use schema_system::SchemaSystem;
|
||||
@ -7,7 +6,6 @@ pub use schema_type::SchemaType;
|
||||
pub use schema_type_declared_class::SchemaTypeDeclaredClass;
|
||||
pub use utl_ts_hash::UtlTsHash;
|
||||
|
||||
pub mod interface;
|
||||
pub mod schema_class_field_data;
|
||||
pub mod schema_class_info;
|
||||
pub mod schema_system;
|
||||
|
@ -1,40 +1,73 @@
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::mem::Address;
|
||||
use crate::remote::Process;
|
||||
|
||||
use super::SchemaType;
|
||||
|
||||
/// Represents a class field in a schema.
|
||||
use crate::util::{Address, Process};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
/// Represents data for a field in a schema class.
|
||||
pub struct SchemaClassFieldData<'a> {
|
||||
process: &'a Process,
|
||||
|
||||
/// Address of the class field.
|
||||
addr: Address,
|
||||
address: Address,
|
||||
}
|
||||
|
||||
impl<'a> SchemaClassFieldData<'a> {
|
||||
pub fn new(process: &'a Process, addr: Address) -> Self {
|
||||
Self { process, addr }
|
||||
/// Creates a new `SchemaClassFieldData` instance.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `process` - A reference to the `Process` struct.
|
||||
/// * `address` - The address of the `SchemaClassFieldData` instance.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `SchemaClassFieldData` - The new `SchemaClassFieldData` instance.
|
||||
pub fn new(process: &'a Process, address: Address) -> Self {
|
||||
Self { process, address }
|
||||
}
|
||||
|
||||
/// Returns the name of the field.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `SchemaClassFieldData` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<String>` - The name of the field.
|
||||
pub fn name(&self) -> Result<String> {
|
||||
let name_ptr = self.process.read_memory::<usize>(self.addr + 0x0)?;
|
||||
let name_ptr = self.process.read_memory::<usize>(self.address)?;
|
||||
|
||||
self.process.read_string_len(name_ptr.into(), 64)
|
||||
self.process.read_string_length(name_ptr.into(), 64)
|
||||
}
|
||||
|
||||
/// Returns the type of the field.
|
||||
/// Returns the `SchemaType` of the field.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `SchemaClassFieldData` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<SchemaType>` - The `SchemaType` of the field.
|
||||
pub fn r#type(&self) -> Result<SchemaType> {
|
||||
Ok(SchemaType::new(
|
||||
self.process,
|
||||
self.process.read_memory::<usize>(self.addr + 0x8)?.into(),
|
||||
self.process
|
||||
.read_memory::<usize>(self.address + 0x8)?
|
||||
.into(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Returns the offset of the field.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `SchemaClassFieldData` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<u16>` - The offset of the field.
|
||||
pub fn offset(&self) -> Result<u16> {
|
||||
self.process.read_memory::<u16>(self.addr + 0x10)
|
||||
self.process.read_memory::<u16>(self.address + 0x10)
|
||||
}
|
||||
}
|
||||
|
@ -1,47 +1,70 @@
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::mem::Address;
|
||||
use crate::remote::Process;
|
||||
|
||||
use super::SchemaClassFieldData;
|
||||
|
||||
/// Represents a class in a schema.
|
||||
use crate::util::{Address, Process};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
/// Represents information about a schema class.
|
||||
pub struct SchemaClassInfo<'a> {
|
||||
process: &'a Process,
|
||||
|
||||
/// Address of the class.
|
||||
addr: Address,
|
||||
|
||||
/// Name of the class.
|
||||
address: Address,
|
||||
class_name: String,
|
||||
}
|
||||
|
||||
impl<'a> SchemaClassInfo<'a> {
|
||||
pub fn new(process: &'a Process, addr: Address, class_name: &str) -> Self {
|
||||
/// Creates a new `SchemaClassInfo` instance.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `process` - A reference to the `Process` struct.
|
||||
/// * `address` - The address of the `SchemaClassInfo` instance.
|
||||
/// * `class_name` - The name of the class.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `SchemaClassInfo` - The new `SchemaClassInfo` instance.
|
||||
pub fn new(process: &'a Process, address: Address, class_name: &str) -> Self {
|
||||
Self {
|
||||
process,
|
||||
addr,
|
||||
address,
|
||||
class_name: class_name.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the name of the class.
|
||||
/// Returns a reference to the name of the class.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `SchemaClassInfo` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `&str` - A string slice containing the name of the class.
|
||||
#[inline]
|
||||
pub fn name(&self) -> &str {
|
||||
&self.class_name
|
||||
}
|
||||
|
||||
/// Returns a list of fields in the class.
|
||||
/// Returns a vector of `SchemaClassFieldData` representing the fields of the schema class.
|
||||
/// If the address of the schema class is null, an empty vector is returned.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `SchemaClassInfo` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<Vec<SchemaClassFieldData>>` - A vector of `SchemaClassFieldData` representing the fields of the schema class.
|
||||
pub fn fields(&self) -> Result<Vec<SchemaClassFieldData>> {
|
||||
let addr = self.process.read_memory::<usize>(self.addr + 0x28)?;
|
||||
let address = self.process.read_memory::<usize>(self.address + 0x28)?;
|
||||
|
||||
if addr == 0 {
|
||||
if address == 0 {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let count = self.fields_count()?;
|
||||
|
||||
let fields: Vec<SchemaClassFieldData> = (addr..addr + count as usize * 0x20)
|
||||
let fields: Vec<SchemaClassFieldData> = (address..address + count as usize * 0x20)
|
||||
.step_by(0x20)
|
||||
.map(|address| SchemaClassFieldData::new(self.process, address.into()))
|
||||
.collect();
|
||||
@ -50,21 +73,38 @@ impl<'a> SchemaClassInfo<'a> {
|
||||
}
|
||||
|
||||
/// Returns the number of fields in the class.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `SchemaClassInfo` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<u16>` - The number of fields in the class.
|
||||
pub fn fields_count(&self) -> Result<u16> {
|
||||
self.process.read_memory::<u16>(self.addr + 0x1C)
|
||||
self.process.read_memory::<u16>(self.address + 0x1C)
|
||||
}
|
||||
|
||||
/// Returns the parent class.
|
||||
/// Returns the parent `SchemaClassInfo` of the current `SchemaClassInfo` instance.
|
||||
/// If the parent is not found, returns `Ok(None)`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `SchemaClassInfo` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<Option<SchemaClassInfo>>` - The parent `SchemaClassInfo` of the current `SchemaClassInfo` instance.
|
||||
pub fn parent(&self) -> Result<Option<SchemaClassInfo>> {
|
||||
let addr = self.process.read_memory::<usize>(self.addr + 0x38)?;
|
||||
let address = Address::from(self.process.read_memory::<usize>(self.address + 0x38)?);
|
||||
|
||||
if addr == 0 {
|
||||
if address.is_zero() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let parent = self.process.read_memory::<usize>((addr + 0x8).into())?;
|
||||
let parent = Address::from(self.process.read_memory::<usize>(address + 0x8)?);
|
||||
|
||||
let name_ptr = self.process.read_memory::<usize>((parent + 0x8).into())?;
|
||||
let name_ptr = self.process.read_memory::<usize>(parent + 0x8)?;
|
||||
let name = self.process.read_string(name_ptr.into())?;
|
||||
|
||||
Ok(Some(SchemaClassInfo::new(
|
||||
|
@ -1,43 +1,57 @@
|
||||
use std::mem;
|
||||
use super::SchemaSystemTypeScope;
|
||||
|
||||
use crate::util::{Address, Process};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
use crate::mem::Address;
|
||||
use crate::remote::Process;
|
||||
|
||||
use super::SchemaSystemTypeScope;
|
||||
use std::mem;
|
||||
|
||||
/// Represents the schema system.
|
||||
pub struct SchemaSystem<'a> {
|
||||
process: &'a Process,
|
||||
|
||||
/// Address of the schema system.
|
||||
addr: Address,
|
||||
address: Address,
|
||||
}
|
||||
|
||||
impl<'a> SchemaSystem<'a> {
|
||||
/// Creates a new `SchemaSystem` instance.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `process` - A reference to the `Process` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<SchemaSystem>` - The new `SchemaSystem` instance.
|
||||
pub fn new(process: &'a Process) -> Result<Self> {
|
||||
let mut addr = process.find_pattern(
|
||||
let mut address = process.find_pattern(
|
||||
"schemasystem.dll",
|
||||
"48 8D 0D ? ? ? ? E9 ? ? ? ? CC CC CC CC 48 8D 0D ? ? ? ? E9 ? ? ? ? CC CC CC CC 48 83 EC 28"
|
||||
)?;
|
||||
).expect("Failed to find SchemaSystem pattern");
|
||||
|
||||
addr = process.resolve_rip(addr, None, None)?;
|
||||
address = process.resolve_rip(address, 0x3, 0x7)?;
|
||||
|
||||
Ok(Self { process, addr })
|
||||
Ok(Self { process, address })
|
||||
}
|
||||
|
||||
/// Returns a list of type scopes.
|
||||
/// Returns a vector of `SchemaSystemTypeScope` objects.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `SchemaSystem` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<Vec<SchemaSystemTypeScope>>` - A vector of `SchemaSystemTypeScope` objects.
|
||||
pub fn type_scopes(&self) -> Result<Vec<SchemaSystemTypeScope>> {
|
||||
let size = self.process.read_memory::<u32>(self.addr + 0x190)?;
|
||||
let size = self.process.read_memory::<u32>(self.address + 0x190)?;
|
||||
|
||||
if size == 0 {
|
||||
bail!("Type scopes size is 0");
|
||||
}
|
||||
|
||||
let data = self.process.read_memory::<usize>(self.addr + 0x198)?;
|
||||
let data = self.process.read_memory::<usize>(self.address + 0x198)?;
|
||||
|
||||
let mut addresses: Vec<usize> = vec![0; size as usize];
|
||||
let mut addresses = vec![0; size as usize];
|
||||
|
||||
self.process.read_memory_raw(
|
||||
data.into(),
|
||||
@ -47,7 +61,7 @@ impl<'a> SchemaSystem<'a> {
|
||||
|
||||
let type_scopes: Vec<SchemaSystemTypeScope> = addresses
|
||||
.iter()
|
||||
.map(|&addr| SchemaSystemTypeScope::new(self.process, addr.into()))
|
||||
.map(|&address| SchemaSystemTypeScope::new(self.process, address.into()))
|
||||
.collect();
|
||||
|
||||
Ok(type_scopes)
|
||||
|
@ -1,49 +1,73 @@
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::mem::Address;
|
||||
use crate::remote::Process;
|
||||
|
||||
use super::{SchemaClassInfo, SchemaTypeDeclaredClass, UtlTsHash};
|
||||
|
||||
/// Represents a schema system type scope.
|
||||
use crate::util::{Address, Process};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
/// Represents a system type scope in the schema.
|
||||
pub struct SchemaSystemTypeScope<'a> {
|
||||
process: &'a Process,
|
||||
|
||||
/// Address of the schema system type scope.
|
||||
addr: Address,
|
||||
address: Address,
|
||||
}
|
||||
|
||||
impl<'a> SchemaSystemTypeScope<'a> {
|
||||
pub fn new(process: &'a Process, addr: Address) -> Self {
|
||||
Self { process, addr }
|
||||
/// Creates a new `SchemaSystemTypeScope` instance.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `process` - A reference to the `Process` struct.
|
||||
/// * `address` - The address of the `SchemaSystemTypeScope` instance.
|
||||
/// * `class_name` - The name of the class.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `SchemaSystemTypeScope` - The new `SchemaSystemTypeScope` instance.
|
||||
pub fn new(process: &'a Process, address: Address) -> Self {
|
||||
Self { process, address }
|
||||
}
|
||||
|
||||
/// Returns a list of classes in the type scope.
|
||||
/// Returns a vector of `SchemaClassInfo` containing information about all the classes declared in the current scope.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `SchemaSystemTypeScope` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<Vec<SchemaClassInfo>>` - A vector of `SchemaClassInfo` containing information about all the classes declared in the current scope.
|
||||
pub fn classes(&self) -> Result<Vec<SchemaClassInfo>> {
|
||||
let declared_classes = self
|
||||
.process
|
||||
.read_memory::<UtlTsHash<*mut SchemaTypeDeclaredClass>>(self.addr + 0x588)?;
|
||||
.read_memory::<UtlTsHash<*mut SchemaTypeDeclaredClass>>(self.address + 0x588)?;
|
||||
|
||||
let classes: Vec<SchemaClassInfo> = declared_classes
|
||||
.elements(self.process)?
|
||||
.iter()
|
||||
.filter_map(|&addr| {
|
||||
let addr = Address::from(addr as usize);
|
||||
.filter_map(|&a| {
|
||||
let address = Address::from(a as usize);
|
||||
|
||||
let declared_class = SchemaTypeDeclaredClass::new(self.process, addr);
|
||||
let declared_class = SchemaTypeDeclaredClass::new(self.process, address);
|
||||
|
||||
declared_class
|
||||
.name()
|
||||
.ok()
|
||||
.map(|name| SchemaClassInfo::new(self.process, addr, &name))
|
||||
.map(|name| SchemaClassInfo::new(self.process, address, &name))
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(classes)
|
||||
}
|
||||
|
||||
/// Returns the name of the module that the type scope belongs to.
|
||||
/// Returns the name of the module associated with the current `SchemaSystemTypeScope` instance.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `SchemaSystemTypeScope` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<String>` - The name of the module associated with the current `SchemaSystemTypeScope` instance.
|
||||
pub fn module_name(&self) -> Result<String> {
|
||||
self.process.read_string_len(self.addr + 0x8, 256)
|
||||
self.process.read_string_length(self.address + 0x8, 256)
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::collections::HashMap;
|
||||
use crate::util::{Address, Process};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
@ -6,10 +6,9 @@ use lazy_static::lazy_static;
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
use crate::mem::Address;
|
||||
use crate::remote::Process;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Map of type names to their C equivalents.
|
||||
/// Map of type names to their `C` equivalents.
|
||||
const TYPE_MAP: &[(&'static str, &'static str)] = &[
|
||||
("uint8", "uint8_t"),
|
||||
("uint16", "uint16_t"),
|
||||
@ -24,6 +23,8 @@ const TYPE_MAP: &[(&'static str, &'static str)] = &[
|
||||
];
|
||||
|
||||
lazy_static! {
|
||||
/// A static HashMap that maps a string to a Regex pattern.
|
||||
/// The Regex pattern is created by wrapping the string with word boundaries `(\b)`.
|
||||
static ref REGEX_MAP: HashMap<&'static str, Regex> = {
|
||||
let mut map = HashMap::with_capacity(TYPE_MAP.len());
|
||||
|
||||
@ -35,22 +36,38 @@ lazy_static! {
|
||||
};
|
||||
}
|
||||
|
||||
/// Represents a schema type.
|
||||
/// Represents a type in the schema.
|
||||
pub struct SchemaType<'a> {
|
||||
process: &'a Process,
|
||||
|
||||
/// Address of the schema type.
|
||||
addr: Address,
|
||||
address: Address,
|
||||
}
|
||||
|
||||
impl<'a> SchemaType<'a> {
|
||||
pub fn new(process: &'a Process, addr: Address) -> Self {
|
||||
Self { process, addr }
|
||||
/// Creates a new `SchemaType` instance.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `process` - A reference to the `Process` struct.
|
||||
/// * `address` - The address of the `SchemaType` instance.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `SchemaType` - The new `SchemaType` instance.
|
||||
pub fn new(process: &'a Process, address: Address) -> Self {
|
||||
Self { process, address }
|
||||
}
|
||||
|
||||
/// Returns the name of the type.
|
||||
/// Returns the name of the schema type.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `SchemaType` instance.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<String>` - The name of the schema type, wrapped in a `Result` object.
|
||||
pub fn name(&self) -> Result<String> {
|
||||
let name_ptr = self.process.read_memory::<usize>(self.addr + 0x8)?;
|
||||
let name_ptr = self.process.read_memory::<usize>(self.address + 0x8)?;
|
||||
|
||||
let name = self
|
||||
.process
|
||||
@ -61,6 +78,15 @@ impl<'a> SchemaType<'a> {
|
||||
Ok(Self::convert_type_name(&name))
|
||||
}
|
||||
|
||||
/// Converts a schema type name to its `C` equivalent.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `type_name` - A string slice that holds the name of the schema type.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `String` - The `C` equivalent of the schema type name.
|
||||
fn convert_type_name(type_name: &str) -> String {
|
||||
let mut result = type_name.to_string();
|
||||
|
||||
|
@ -1,25 +1,40 @@
|
||||
use crate::util::{Address, Process};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::mem::Address;
|
||||
use crate::remote::Process;
|
||||
|
||||
/// Represents a schema type declared class.
|
||||
/// Represents a declared class type in the schema.
|
||||
pub struct SchemaTypeDeclaredClass<'a> {
|
||||
process: &'a Process,
|
||||
|
||||
/// Address of the schema type declared class.
|
||||
addr: Address,
|
||||
address: Address,
|
||||
}
|
||||
|
||||
impl<'a> SchemaTypeDeclaredClass<'a> {
|
||||
pub fn new(process: &'a Process, addr: Address) -> Self {
|
||||
Self { process, addr }
|
||||
/// Creates a new `SchemaTypeDeclaredClass` instance.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `process` - A reference to the `Process` struct.
|
||||
/// * `address` - The address of the `SchemaTypeDeclaredClass` instance.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `SchemaTypeDeclaredClass` - The new `SchemaTypeDeclaredClass` instance.
|
||||
pub fn new(process: &'a Process, address: Address) -> Self {
|
||||
Self { process, address }
|
||||
}
|
||||
|
||||
/// Returns the name of the class.
|
||||
/// Returns the name of the declared class.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `SchemaTypeDeclaredClass` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<String>` - The name of the declared class.
|
||||
pub fn name(&self) -> Result<String> {
|
||||
let name_ptr = self.process.read_memory::<usize>(self.addr + 0x8)?;
|
||||
let name_ptr = self.process.read_memory::<usize>(self.address + 0x8)?;
|
||||
|
||||
self.process.read_string_len(name_ptr.into(), 64)
|
||||
self.process.read_string_length(name_ptr.into(), 64)
|
||||
}
|
||||
}
|
||||
|
@ -1,44 +1,63 @@
|
||||
use crate::util::Process;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use std::mem::offset_of;
|
||||
|
||||
use crate::remote::Process;
|
||||
|
||||
/// Represents a hash bucket.
|
||||
/// Represents the internal data of a hash table.
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct HashFixedDataInternal<T, K> {
|
||||
struct HashFixedDataInternal<T, K> {
|
||||
ui_key: K, // 0x0010
|
||||
next: *mut HashFixedDataInternal<T, K>, // 0x0010
|
||||
data: T, // 0x0010
|
||||
}
|
||||
|
||||
/// Implementation of HashFixedDataInternal struct with methods for reading the next element in the hash table.
|
||||
impl<T, K> HashFixedDataInternal<T, K> {
|
||||
pub fn next(&self, process: &Process) -> Result<*mut HashFixedDataInternal<T, K>> {
|
||||
/// Reads the next element in the hash table.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `process` - A reference to the `Process` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns a Result containing a pointer to the next element in the hash table if successful, or an error if unsuccessful.
|
||||
fn next(&self, process: &Process) -> Result<*mut HashFixedDataInternal<T, K>> {
|
||||
process.read_memory::<*mut HashFixedDataInternal<T, K>>(
|
||||
(self as *const _ as usize + offset_of!(HashFixedDataInternal<T, K>, next)).into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a hash bucket.
|
||||
/// Represents the internal data of a hash bucket.
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct HashBucketDataInternal<T, K> {
|
||||
struct HashBucketDataInternal<T, K> {
|
||||
data: T, // 0x0000
|
||||
next: *mut HashFixedDataInternal<T, K>, // 0x0008
|
||||
ui_key: K, // 0x0010
|
||||
}
|
||||
|
||||
impl<T, K> HashBucketDataInternal<T, K> {
|
||||
pub fn next(&self, process: &Process) -> Result<*mut HashFixedDataInternal<T, K>> {
|
||||
/// Reads the next element in the hash table.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `process` - A reference to the `Process` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns a Result containing a pointer to the next element in the hash table if successful, or an error if unsuccessful.
|
||||
fn next(&self, process: &Process) -> Result<*mut HashFixedDataInternal<T, K>> {
|
||||
process.read_memory::<*mut HashFixedDataInternal<T, K>>(
|
||||
(self as *const _ as usize + offset_of!(HashBucketDataInternal<T, K>, next)).into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a hash table.
|
||||
/// Represents allocated data in a hash table.
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct HashAllocatedData<T, K> {
|
||||
@ -47,17 +66,26 @@ pub struct HashAllocatedData<T, K> {
|
||||
}
|
||||
|
||||
impl<T, K> HashAllocatedData<T, K> {
|
||||
pub fn list(&self, process: &Process) -> Result<[HashFixedDataInternal<T, K>; 128]> {
|
||||
/// Reads the list of elements in the hash table.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `process` - A reference to the `Process` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns a Result containing a list of elements in the hash table if successful, or an error if unsuccessful.
|
||||
fn list(&self, process: &Process) -> Result<[HashFixedDataInternal<T, K>; 128]> {
|
||||
process.read_memory::<[HashFixedDataInternal<T, K>; 128]>(
|
||||
(self as *const _ as usize + offset_of!(HashAllocatedData<T, K>, list)).into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a hash table.
|
||||
/// A struct representing unallocated data in a hash table.
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct HashUnallocatedData<T, K> {
|
||||
struct HashUnallocatedData<T, K> {
|
||||
next: *mut HashUnallocatedData<T, K>, // 0x0000
|
||||
unknown_1: K, // 0x0008
|
||||
ui_key: K, // 0x0010
|
||||
@ -66,19 +94,46 @@ pub struct HashUnallocatedData<T, K> {
|
||||
}
|
||||
|
||||
impl<T, K> HashUnallocatedData<T, K> {
|
||||
pub fn next(&self, process: &Process) -> Result<*mut HashUnallocatedData<T, K>> {
|
||||
/// Reads the next `HashUnallocatedData` element in memory.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `process` - A reference to the `Process` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<*mut HashUnallocatedData<T, K>>` - A Result containing a pointer to the next `HashUnallocatedData` element in memory.
|
||||
fn next(&self, process: &Process) -> Result<*mut HashUnallocatedData<T, K>> {
|
||||
process.read_memory::<*mut HashUnallocatedData<T, K>>(
|
||||
(self as *const _ as usize + offset_of!(HashUnallocatedData<T, K>, next)).into(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn ui_key(&self, process: &Process) -> Result<K> {
|
||||
/// Reads the UI key of the `HashUnallocatedData` element in memory.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `process` - A reference to the `Process` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<K>` - A Result containing the UI key of the `HashUnallocatedData` element in memory.
|
||||
fn ui_key(&self, process: &Process) -> Result<K> {
|
||||
process.read_memory::<K>(
|
||||
(self as *const _ as usize + offset_of!(HashUnallocatedData<T, K>, ui_key)).into(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn block_list(&self, process: &Process) -> Result<[HashBucketDataInternal<T, K>; 256]> {
|
||||
/// Reads the block list of the `HashUnallocatedData` element in memory.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `process` - A reference to the `Process` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<[HashBucketDataInternal<T, K>; 256]>` - A Result containing the block list of the `HashUnallocatedData` element in memory.
|
||||
fn block_list(&self, process: &Process) -> Result<[HashBucketDataInternal<T, K>; 256]> {
|
||||
process.read_memory::<[HashBucketDataInternal<T, K>; 256]>(
|
||||
(self as *const _ as usize + offset_of!(HashUnallocatedData<T, K>, block_list)).into(),
|
||||
)
|
||||
@ -88,15 +143,16 @@ impl<T, K> HashUnallocatedData<T, K> {
|
||||
/// Represents a hash bucket.
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct HashBucket<T, K> {
|
||||
struct HashBucket<T, K> {
|
||||
pad_0: [u8; 0x10], // 0x0000
|
||||
allocated_data: *const HashAllocatedData<T, K>, // 0x0010
|
||||
unallocated_data: *const HashUnallocatedData<T, K>, // 0x0018
|
||||
}
|
||||
|
||||
/// Represents a memory pool used by the `UtlTsHash` class.
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct UtlMemoryPool {
|
||||
struct UtlMemoryPool {
|
||||
block_size: i32, // 0x0000
|
||||
blocks_per_blob: i32, // 0x0004
|
||||
grow_mode: i32, // 0x0008
|
||||
@ -107,14 +163,30 @@ pub struct UtlMemoryPool {
|
||||
|
||||
impl UtlMemoryPool {
|
||||
/// Returns the number of blocks per blob.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `UtlMemoryPool` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `i32` - The number of blocks per blob.
|
||||
#[inline]
|
||||
pub fn block_size(&self) -> i32 {
|
||||
fn block_size(&self) -> i32 {
|
||||
self.blocks_per_blob
|
||||
}
|
||||
|
||||
/// Returns the number of blocks allocated.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `UtlMemoryPool` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `i32` - The number of blocks allocated.
|
||||
#[inline]
|
||||
pub fn count(&self) -> i32 {
|
||||
fn count(&self) -> i32 {
|
||||
self.block_allocated_size
|
||||
}
|
||||
}
|
||||
@ -132,25 +204,49 @@ where
|
||||
T: Copy,
|
||||
{
|
||||
/// Returns the number of blocks per blob.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `UtlTsHash` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `i32` - The number of blocks per blob.
|
||||
#[inline]
|
||||
pub fn block_size(&self) -> i32 {
|
||||
self.entry_memory.block_size()
|
||||
}
|
||||
|
||||
/// Returns the number of blocks allocated.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `UtlTsHash` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `i32` - The number of blocks allocated.
|
||||
#[inline]
|
||||
pub fn count(&self) -> i32 {
|
||||
self.entry_memory.count()
|
||||
}
|
||||
|
||||
/// Returns a list of elements in the hash table.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `process` - A reference to the `Process` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<Vec<T>>` - A Result containing a list of elements in the hash table if successful, or an error if unsuccessful.
|
||||
pub fn elements(&self, process: &Process) -> Result<Vec<T>> {
|
||||
let mut address = self.buckets.unallocated_data;
|
||||
|
||||
let min_size = (self.block_size() as usize).min(self.count() as usize);
|
||||
|
||||
let mut list = Vec::with_capacity(min_size);
|
||||
|
||||
let mut address = self.buckets.unallocated_data;
|
||||
|
||||
while !address.is_null() {
|
||||
let block_list = unsafe { (*address).block_list(process) }?;
|
||||
|
||||
|
211
src/util/address.rs
Normal file
211
src/util/address.rs
Normal file
@ -0,0 +1,211 @@
|
||||
use std::fmt::{LowerHex, UpperHex};
|
||||
use std::ops::{Add, AddAssign, Sub, SubAssign};
|
||||
|
||||
/// Represents a memory address.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd)]
|
||||
#[repr(transparent)]
|
||||
pub struct Address(pub usize);
|
||||
|
||||
impl Address {
|
||||
/// Adds the given value to the current address and returns a new `Address`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `Address` struct.
|
||||
/// * `value` - The value to add to the current address.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Address` - A new `Address` struct with the value of the current address plus the given value.
|
||||
#[inline]
|
||||
pub fn add(&self, value: usize) -> Self {
|
||||
Self(self.0 + value)
|
||||
}
|
||||
|
||||
/// Returns true if the value of the address is zero.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `Address` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `bool` - True if the value of the address is zero, false otherwise.
|
||||
#[inline]
|
||||
pub fn is_zero(&self) -> bool {
|
||||
self.0 == 0
|
||||
}
|
||||
|
||||
/// Subtracts a value from the current address and returns a new `Address`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `Address` struct.
|
||||
/// * `value` - The value to subtract from the current address.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Address` - A new `Address` struct with the value of the current address minus the given value.
|
||||
#[inline]
|
||||
pub fn sub(&self, value: usize) -> Self {
|
||||
Self(self.0 - value)
|
||||
}
|
||||
|
||||
/// Returns a raw pointer to the underlying data as a `*const T`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `Address` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `*const T` - A raw pointer to the underlying data as a `*const T`.
|
||||
#[inline]
|
||||
pub fn as_ptr<T>(&self) -> *const T {
|
||||
self.0 as *const T
|
||||
}
|
||||
|
||||
/// Returns a mutable pointer to the underlying data.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `Address` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `*mut T` - A mutable pointer to the underlying data.
|
||||
#[inline]
|
||||
pub fn as_mut_ptr<T>(&self) -> *mut T {
|
||||
self.0 as *mut T
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a `usize` value to an `Address` struct.
|
||||
impl From<usize> for Address {
|
||||
fn from(value: usize) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a raw pointer to a `usize` value and creates an `Address` instance from it.
|
||||
impl From<*const u8> for Address {
|
||||
fn from(value: *const u8) -> Self {
|
||||
Self(value as usize)
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a raw pointer to a `usize` value and wraps it in an `Address` struct.
|
||||
impl From<*mut u8> for Address {
|
||||
fn from(value: *mut u8) -> Self {
|
||||
Self(value as usize)
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts an `Address` struct to a `usize` value.
|
||||
impl From<Address> for usize {
|
||||
fn from(value: Address) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts an `Address` struct to a raw pointer to an unsigned 8-bit integer.
|
||||
impl From<Address> for *const u8 {
|
||||
fn from(value: Address) -> Self {
|
||||
value.0 as *const u8
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts an `Address` struct to a raw pointer to a mutable unsigned 8-bit integer.
|
||||
impl From<Address> for *mut u8 {
|
||||
fn from(value: Address) -> Self {
|
||||
value.0 as *mut u8
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the addition of a `usize` value to an `Address` value.
|
||||
impl Add<usize> for Address {
|
||||
type Output = Self;
|
||||
|
||||
/// Adds a `usize` value to an `Address` value and returns the result.
|
||||
fn add(self, rhs: usize) -> Self::Output {
|
||||
Self(self.0 + rhs)
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the addition of two `Address` instances.
|
||||
impl Add<Address> for Address {
|
||||
type Output = Self;
|
||||
|
||||
/// Adds two `Address` instances and returns a new `Address`.
|
||||
fn add(self, rhs: Address) -> Self::Output {
|
||||
Self(self.0 + rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the `AddAssign` trait for `Address` struct.
|
||||
impl AddAssign<usize> for Address {
|
||||
/// Adds the given `rhs` value to the `Address` struct.
|
||||
fn add_assign(&mut self, rhs: usize) {
|
||||
self.0 += rhs;
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the `AddAssign` trait for `Address`.
|
||||
impl AddAssign<Address> for Address {
|
||||
/// Adds the given `rhs` value to the `Address` struct.
|
||||
fn add_assign(&mut self, rhs: Address) {
|
||||
self.0 += rhs.0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the subtraction of a `usize` from an `Address`.
|
||||
impl Sub<usize> for Address {
|
||||
type Output = Self;
|
||||
|
||||
/// Subtracts the given `rhs` from `&self`.
|
||||
fn sub(self, rhs: usize) -> Self::Output {
|
||||
Self(self.0 - rhs)
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the subtraction operation for `Address`.
|
||||
impl Sub<Address> for Address {
|
||||
type Output = Self;
|
||||
|
||||
/// Subtracts the given `rhs` from `&self`.
|
||||
fn sub(self, rhs: Address) -> Self::Output {
|
||||
Self(self.0 - rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the subtraction assignment operation for `Address` and `usize`.
|
||||
impl SubAssign<usize> for Address {
|
||||
/// Subtracts the given `rhs` from the `Address`.
|
||||
fn sub_assign(&mut self, rhs: usize) {
|
||||
self.0 -= rhs;
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the subtraction assignment operator for `Address`.
|
||||
impl SubAssign<Address> for Address {
|
||||
/// Subtracts the value of `rhs` from `&self`.
|
||||
fn sub_assign(&mut self, rhs: Address) {
|
||||
self.0 -= rhs.0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the `UpperHex` trait for the `Address` struct.
|
||||
impl UpperHex for Address {
|
||||
/// Formats the value of `&self` using the `write!` macro and the `UpperHex` format specifier.
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:#X}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the `LowerHex` trait for the `Address` struct.
|
||||
impl LowerHex for Address {
|
||||
/// Formats the value of `&self` using the `write!` macro and the `LowerHex` format specifier.
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:#x}", self.0)
|
||||
}
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
pub use address::Address;
|
||||
pub use module::Module;
|
||||
pub use process::Process;
|
||||
|
||||
pub mod address;
|
||||
pub mod module;
|
||||
pub mod process;
|
156
src/util/module.rs
Normal file
156
src/util/module.rs
Normal file
@ -0,0 +1,156 @@
|
||||
use super::Address;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use goblin::pe::export::Export;
|
||||
use goblin::pe::import::Import;
|
||||
use goblin::pe::options::ParseOptions;
|
||||
use goblin::pe::section_table::SectionTable;
|
||||
use goblin::pe::PE;
|
||||
|
||||
/// Represents a module loaded into the process.
|
||||
pub struct Module<'a> {
|
||||
/// The name of the module.
|
||||
pub name: &'a str,
|
||||
|
||||
/// A reference to a slice of bytes containing the module data.
|
||||
pub data: &'a [u8],
|
||||
|
||||
/// The PE file format representation of the module.
|
||||
pub pe: PE<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Module<'a> {
|
||||
/// Parses the given module name and data and returns a `Result` containing a `Module` struct.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - A string slice that holds the name of the module.
|
||||
/// * `data` - A byte slice that holds the data of the module.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<Self>` - A `Result` containing a `Module` instance if successful, or an error if the module could not be parsed.
|
||||
pub fn parse(name: &'a str, data: &'a [u8]) -> Result<Self> {
|
||||
let pe = PE::parse_with_opts(
|
||||
data,
|
||||
&ParseOptions {
|
||||
parse_attribute_certificates: false,
|
||||
resolve_rva: false,
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(Self { name, data, pe })
|
||||
}
|
||||
|
||||
/// Returns the base address of the module.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `Module` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Address` - The base address of the module.
|
||||
#[inline]
|
||||
pub fn base(&self) -> Address {
|
||||
self.pe.image_base.into()
|
||||
}
|
||||
|
||||
/// Returns a slice of `Export` structs representing the exports of the module.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `Module` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `&[Export]` - A slice of `Export` structs representing the exports of the module.
|
||||
#[inline]
|
||||
pub fn exports(&self) -> &'a [Export] {
|
||||
self.pe.exports.as_slice()
|
||||
}
|
||||
|
||||
/// Returns the address of the export with the given name, if it exists.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `Module` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Option<Address>` - The address of the export with the given name, if it exists.
|
||||
#[inline]
|
||||
pub fn get_export_by_name(&self, name: &str) -> Option<Address> {
|
||||
self.pe
|
||||
.exports
|
||||
.iter()
|
||||
.find(|e| e.name.unwrap() == name)
|
||||
.map(|e| (self.pe.image_base + e.rva).into())
|
||||
}
|
||||
|
||||
/// Returns the address of the imported function with the given name, if it exists.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `Module` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Option<Address>` - The address of the imported function with the given name, if it exists.
|
||||
#[inline]
|
||||
pub fn get_import_by_name(&self, name: &str) -> Option<Address> {
|
||||
self.pe
|
||||
.imports
|
||||
.iter()
|
||||
.find(|i| i.name.to_string() == name)
|
||||
.map(|i| (self.pe.image_base + i.rva).into())
|
||||
}
|
||||
|
||||
/// Returns a slice of the imported functions of the module.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `Module` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `&[Import]` - A slice of `Import` structs representing the imported functions of the module.
|
||||
#[inline]
|
||||
pub fn imports(&self) -> &'a [Import] {
|
||||
self.pe.imports.as_slice()
|
||||
}
|
||||
|
||||
/// Returns a slice of the section table entries in the module's PE header.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `Module` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `&[SectionTable]` - A slice of `SectionTable` structs representing the section table entries in the module's PE header.
|
||||
#[inline]
|
||||
pub fn sections(&self) -> &[SectionTable] {
|
||||
self.pe.sections.as_slice()
|
||||
}
|
||||
|
||||
/// Returns the size of the module.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `Module` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `u32` - The size of the module.
|
||||
#[inline]
|
||||
pub fn size(&self) -> u32 {
|
||||
self.pe
|
||||
.header
|
||||
.optional_header
|
||||
.unwrap()
|
||||
.windows_fields
|
||||
.size_of_image
|
||||
}
|
||||
}
|
371
src/util/process.rs
Normal file
371
src/util/process.rs
Normal file
@ -0,0 +1,371 @@
|
||||
use super::{Address, Module};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::{c_void, CStr};
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
|
||||
use windows::Win32::Foundation::{CloseHandle, HANDLE};
|
||||
use windows::Win32::System::Diagnostics::Debug::ReadProcessMemory;
|
||||
use windows::Win32::System::Diagnostics::ToolHelp::*;
|
||||
use windows::Win32::System::Threading::{OpenProcess, PROCESS_ALL_ACCESS};
|
||||
|
||||
/// Represents a Windows process.
|
||||
#[derive(Debug)]
|
||||
pub struct Process {
|
||||
/// ID of the process.
|
||||
id: u32,
|
||||
|
||||
/// Handle to the process.
|
||||
handle: HANDLE,
|
||||
|
||||
/// A HashMap containing the name of each module and its corresponding raw data.
|
||||
modules: HashMap<String, Vec<u8>>,
|
||||
}
|
||||
|
||||
impl Process {
|
||||
/// Creates a new `Process` instance with the given name.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - A string slice that holds the name of the process.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<Self>` - A `Result` containing the `Process` instance if successful, or an error if the process could not be found.
|
||||
pub fn new(name: &str) -> Result<Self> {
|
||||
let id = Self::get_process_id_by_name(name)?;
|
||||
|
||||
let handle = unsafe { OpenProcess(PROCESS_ALL_ACCESS, false, id) }?;
|
||||
|
||||
Ok(Self {
|
||||
id,
|
||||
handle,
|
||||
modules: HashMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Initializes the process by parsing the loaded modules.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A mutable reference to the `Process` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<()>` - A `Result` indicating the outcome of the operation.
|
||||
pub fn initialize(&mut self) -> Result<()> {
|
||||
self.parse_loaded_modules()
|
||||
}
|
||||
|
||||
/// Searches for a pattern in the memory of a specified module and returns the address of the first occurrence.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `Process` struct.
|
||||
/// * `module_name` - A string slice that holds the name of the module to search in.
|
||||
/// * `pattern` - A string slice that holds the pattern to search for.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Option<Address>` - The address of the first occurrence of the pattern if found, or `None` if the pattern was not found.
|
||||
pub fn find_pattern(&self, module_name: &str, pattern: &str) -> Option<Address> {
|
||||
let module = self.get_module_by_name(module_name)?;
|
||||
|
||||
let pattern_bytes = Self::pattern_to_bytes(pattern);
|
||||
|
||||
for (i, window) in module.data.windows(pattern_bytes.len()).enumerate() {
|
||||
if window
|
||||
.iter()
|
||||
.zip(&pattern_bytes)
|
||||
.all(|(&x, &y)| x == y as u8 || y == -1)
|
||||
{
|
||||
return Some(module.base() + i);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns an optional `Module` instance by its name.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `Process` struct.
|
||||
/// * `name` - A string slice representing the name of the module to retrieve.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Option<Module>` - An optional `Module` instance if the module was found, or `None` if the module was not found.
|
||||
pub fn get_module_by_name<'a>(&'a self, name: &'a str) -> Option<Module<'a>> {
|
||||
self.modules
|
||||
.get(name)
|
||||
.map(|data| Module::parse(name, data).unwrap())
|
||||
}
|
||||
|
||||
/// Returns a vector of `Module` instances parsed from the process's loaded modules.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `Process` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<Vec<Module>>` - A `Result` containing a vector of `Module` instances if successful, or an error if the modules could not be parsed.
|
||||
pub fn modules(&self) -> Result<Vec<Module>> {
|
||||
let mut modules = Vec::new();
|
||||
|
||||
for (name, data) in &self.modules {
|
||||
modules.push(Module::parse(name, data)?);
|
||||
}
|
||||
|
||||
Ok(modules)
|
||||
}
|
||||
|
||||
/// Reads the memory at the specified address and returns the value as type T.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `Process` struct.
|
||||
/// * `address` - The address to read from.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// let process = Process::new(pid)?;
|
||||
/// let value: i32 = process.read_memory(address)?;
|
||||
/// ```
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<T>` - A `Result` containing the value if successful, or an error if the memory read fails.
|
||||
pub fn read_memory<T>(&self, address: Address) -> Result<T> {
|
||||
let mut buffer: T = unsafe { mem::zeroed() };
|
||||
|
||||
self.read_memory_raw(
|
||||
address,
|
||||
&mut buffer as *const _ as *mut _,
|
||||
mem::size_of::<T>(),
|
||||
)?;
|
||||
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
/// Reads the memory of a process and stores it in a buffer.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `Process` struct.
|
||||
/// * `address` - The address to start reading from.
|
||||
/// * `buffer` - A pointer to the buffer where the read data will be stored.
|
||||
/// * `size` - The number of bytes to read.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<()>` - A `Result` indicating the outcome of the operation.
|
||||
pub fn read_memory_raw(
|
||||
&self,
|
||||
address: Address,
|
||||
buffer: *mut c_void,
|
||||
size: usize,
|
||||
) -> Result<()> {
|
||||
unsafe {
|
||||
ReadProcessMemory(
|
||||
self.handle,
|
||||
address.as_ptr(),
|
||||
buffer,
|
||||
size,
|
||||
Some(ptr::null_mut()),
|
||||
)
|
||||
}
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
/// Reads a null-terminated string from the process memory at the given address.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `Process` struct.
|
||||
/// * `address` - The address in the process memory where the string is located.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<String>` - A `Result` containing the string read from the process memory if successful, or an error if the memory read fails or if the string contains invalid UTF-8.
|
||||
pub fn read_string(&self, address: Address) -> Result<String> {
|
||||
let mut buffer = Vec::new();
|
||||
|
||||
for i in 0.. {
|
||||
match self.read_memory::<u8>(address + i) {
|
||||
Ok(byte) if byte != 0 => buffer.push(byte),
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(String::from_utf8(buffer)?)
|
||||
}
|
||||
|
||||
/// Reads a string of the specified length from the process memory at the given address.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `Process` struct.
|
||||
/// * `address` - The address to read the string from.
|
||||
/// * `length` - The length of the string to read.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<String>` - A `Result` containing the string read from the process memory if successful, or an error if the memory read fails or if the string contains invalid UTF-8.
|
||||
pub fn read_string_length(&self, address: Address, length: usize) -> Result<String> {
|
||||
let mut buffer = vec![0; length];
|
||||
|
||||
self.read_memory_raw(address, buffer.as_mut_ptr() as *mut _, length)?;
|
||||
|
||||
if let Some(end) = buffer.iter().position(|&x| x == 0) {
|
||||
buffer.truncate(end);
|
||||
}
|
||||
|
||||
Ok(String::from_utf8(buffer)?)
|
||||
}
|
||||
|
||||
/// Resolves a jump instruction at the given address by calculating the target address based on the
|
||||
/// displacement value at the given offset.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `Process` struct.
|
||||
/// * `address` - The address of the jump instruction.
|
||||
/// * `offset` - The offset of the displacement value.
|
||||
/// * `length` - The length of the instruction.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<Address>` - A `Result` containing the absolute address if successful, or an error if the memory read fails.
|
||||
pub fn resolve_jmp(&self, address: Address, offset: usize, length: usize) -> Result<Address> {
|
||||
let displacement = self.read_memory::<i32>(address.add(offset))?;
|
||||
|
||||
Ok(((address.add(length).0 as isize + displacement as isize) as usize).into())
|
||||
}
|
||||
|
||||
/// Resolves the absolute address of a relative instruction pointer (RIP) address.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A reference to the `Process` struct.
|
||||
/// * `address` - The address of the relative instruction pointer (RIP).
|
||||
/// * `offset` - The offset of the displacement value.
|
||||
/// * `length` - The length of the instruction.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<Address>` - A `Result` containing the absolute address if successful, or an error if the memory read fails.
|
||||
pub fn resolve_rip(&self, address: Address, offset: usize, length: usize) -> Result<Address> {
|
||||
let displacement = self.read_memory::<i32>(address.add(offset))?;
|
||||
|
||||
Ok(((address.add(length).0 as isize + displacement as isize) as usize).into())
|
||||
}
|
||||
|
||||
/// Returns the process ID of the first process with the given name.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `process_name` - A string slice that holds the name of the process to search for.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<u32>` - A `Result` containing the process ID if successful, or an error if the process could not be found.
|
||||
fn get_process_id_by_name(process_name: &str) -> Result<u32> {
|
||||
let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) }?;
|
||||
|
||||
let mut entry = PROCESSENTRY32 {
|
||||
dwSize: mem::size_of::<PROCESSENTRY32>() as u32,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
unsafe {
|
||||
Process32First(snapshot, &mut entry)?;
|
||||
|
||||
while Process32Next(snapshot, &mut entry).is_ok() {
|
||||
let name = CStr::from_ptr(&entry.szExeFile as *const _ as *const _).to_str()?;
|
||||
|
||||
if name == process_name {
|
||||
return Ok(entry.th32ProcessID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bail!("Process not found: {}", process_name)
|
||||
}
|
||||
|
||||
/// Parses the loaded modules of a process and stores them in a HashMap with the module name as the key and the module data as the value.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `&self` - A mutable reference to the `Process` struct.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<()>` - A `Result` indicating the outcome of the operation.
|
||||
fn parse_loaded_modules(&mut self) -> Result<()> {
|
||||
let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, self.id) }?;
|
||||
|
||||
let mut entry = MODULEENTRY32 {
|
||||
dwSize: mem::size_of::<MODULEENTRY32>() as u32,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
unsafe {
|
||||
Module32First(snapshot, &mut entry)?;
|
||||
|
||||
while Module32Next(snapshot, &mut entry).is_ok() {
|
||||
let name = CStr::from_ptr(&entry.szModule as *const _ as *const _).to_str()?;
|
||||
|
||||
let mut data = vec![0; entry.modBaseSize as usize];
|
||||
|
||||
self.read_memory_raw(
|
||||
entry.modBaseAddr.into(),
|
||||
data.as_mut_ptr() as *mut _,
|
||||
data.len(),
|
||||
)?;
|
||||
|
||||
self.modules.insert(name.to_string(), data);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Converts a pattern string to a vector of bytes.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `pattern` - A string slice that represents the pattern to be converted.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Vec<i32>` - A vector of bytes representing the pattern.
|
||||
fn pattern_to_bytes(pattern: &str) -> Vec<i32> {
|
||||
pattern
|
||||
.split_whitespace()
|
||||
.map(|s| {
|
||||
if s == "?" {
|
||||
-1
|
||||
} else {
|
||||
i32::from_str_radix(s, 16).unwrap_or(0)
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the `Drop` trait for the `Process` struct.
|
||||
///
|
||||
/// When a `Process` instance goes out of scope, this implementation will automatically close the process handle if it is not invalid.
|
||||
impl Drop for Process {
|
||||
fn drop(&mut self) {
|
||||
if !self.handle.is_invalid() {
|
||||
unsafe { CloseHandle(self.handle).unwrap() }
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user