mirror of
https://github.com/a2x/cs2-dumper.git
synced 2025-10-07 16:30:01 +08:00
Rewrote project in Rust
This commit is contained in:
40
src/builder/cpp_file_builder.rs
Normal file
40
src/builder/cpp_file_builder.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use std::io::{Result, Write};
|
||||
|
||||
use super::FileBuilder;
|
||||
|
||||
pub struct CppBuilder;
|
||||
|
||||
impl FileBuilder for CppBuilder {
|
||||
fn extension(&mut self) -> &str {
|
||||
"hpp"
|
||||
}
|
||||
|
||||
fn write_top_level(&mut self, output: &mut dyn Write) -> Result<()> {
|
||||
write!(output, "#pragma once\n\n")?;
|
||||
write!(output, "#include <cstddef>\n\n")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_namespace(&mut self, output: &mut dyn Write, name: &str) -> Result<()> {
|
||||
write!(output, "namespace {} {{\n", name)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_variable(&mut self, output: &mut dyn Write, name: &str, value: usize) -> Result<()> {
|
||||
write!(
|
||||
output,
|
||||
" constexpr std::ptrdiff_t {} = {:#X};\n",
|
||||
name, value
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_closure(&mut self, output: &mut dyn Write, eof: bool) -> Result<()> {
|
||||
write!(output, "{}", if eof { "}" } else { "}\n\n" })?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
33
src/builder/csharp_file_builder.rs
Normal file
33
src/builder/csharp_file_builder.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use std::io::{Result, Write};
|
||||
|
||||
use super::FileBuilder;
|
||||
|
||||
pub struct CSharpBuilder;
|
||||
|
||||
impl FileBuilder for CSharpBuilder {
|
||||
fn extension(&mut self) -> &str {
|
||||
"cs"
|
||||
}
|
||||
|
||||
fn write_top_level(&mut self, _output: &mut dyn Write) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_namespace(&mut self, output: &mut dyn Write, name: &str) -> Result<()> {
|
||||
write!(output, "public static class {} {{\n", name)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_variable(&mut self, output: &mut dyn Write, name: &str, value: usize) -> Result<()> {
|
||||
write!(output, " public const nint {} = {:#X};\n", name, value)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_closure(&mut self, output: &mut dyn Write, eof: bool) -> Result<()> {
|
||||
write!(output, "{}", if eof { "}" } else { "}\n\n" })?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
9
src/builder/file_builder.rs
Normal file
9
src/builder/file_builder.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use std::io::{Result, Write};
|
||||
|
||||
pub trait FileBuilder {
|
||||
fn extension(&mut self) -> &str;
|
||||
fn write_top_level(&mut self, output: &mut dyn Write) -> Result<()>;
|
||||
fn write_namespace(&mut self, output: &mut dyn Write, name: &str) -> Result<()>;
|
||||
fn write_variable(&mut self, output: &mut dyn Write, name: &str, value: usize) -> Result<()>;
|
||||
fn write_closure(&mut self, output: &mut dyn Write, eof: bool) -> Result<()>;
|
||||
}
|
56
src/builder/json_file_builder.rs
Normal file
56
src/builder/json_file_builder.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use std::io::{Result, Write};
|
||||
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use super::FileBuilder;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct JsonFileBuilder {
|
||||
json: Value,
|
||||
namespace: String,
|
||||
}
|
||||
|
||||
impl FileBuilder for JsonFileBuilder {
|
||||
fn extension(&mut self) -> &str {
|
||||
"json"
|
||||
}
|
||||
|
||||
fn write_top_level(&mut self, _output: &mut dyn Write) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_namespace(&mut self, _output: &mut dyn Write, name: &str) -> Result<()> {
|
||||
self.namespace = name.to_string();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_variable(&mut self, _output: &mut dyn Write, name: &str, value: usize) -> Result<()> {
|
||||
if let Some(json_as_map) = self.json.as_object_mut() {
|
||||
let namespace_entry = json_as_map
|
||||
.entry(self.namespace.clone())
|
||||
.or_insert_with(|| json!({}));
|
||||
|
||||
if let Some(namespace_object) = namespace_entry.as_object_mut() {
|
||||
namespace_object.insert(name.to_string(), json!(value));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_closure(&mut self, output: &mut dyn Write, eof: bool) -> Result<()> {
|
||||
if eof {
|
||||
write!(
|
||||
output,
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&self.json).unwrap()
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
self.json = json!({});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
71
src/builder/mod.rs
Normal file
71
src/builder/mod.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
pub use std::io::{Result, Write};
|
||||
|
||||
pub use cpp_file_builder::CppBuilder;
|
||||
pub use csharp_file_builder::CSharpBuilder;
|
||||
pub use file_builder::FileBuilder;
|
||||
pub use json_file_builder::JsonFileBuilder;
|
||||
pub use rust_file_builder::RustFileBuilder;
|
||||
|
||||
pub mod cpp_file_builder;
|
||||
pub mod csharp_file_builder;
|
||||
pub mod file_builder;
|
||||
pub mod json_file_builder;
|
||||
pub mod rust_file_builder;
|
||||
|
||||
pub enum FileBuilderEnum {
|
||||
CppBuilder(CppBuilder),
|
||||
CSharpBuilder(CSharpBuilder),
|
||||
JsonFileBuilder(JsonFileBuilder),
|
||||
RustFileBuilder(RustFileBuilder),
|
||||
}
|
||||
|
||||
impl FileBuilder for FileBuilderEnum {
|
||||
fn extension(&mut self) -> &str {
|
||||
match self {
|
||||
FileBuilderEnum::CppBuilder(builder) => builder.extension(),
|
||||
FileBuilderEnum::CSharpBuilder(builder) => builder.extension(),
|
||||
FileBuilderEnum::JsonFileBuilder(builder) => builder.extension(),
|
||||
FileBuilderEnum::RustFileBuilder(builder) => builder.extension(),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_top_level(&mut self, output: &mut dyn Write) -> Result<()> {
|
||||
match self {
|
||||
FileBuilderEnum::CppBuilder(builder) => builder.write_top_level(output),
|
||||
FileBuilderEnum::CSharpBuilder(builder) => builder.write_top_level(output),
|
||||
FileBuilderEnum::JsonFileBuilder(builder) => builder.write_top_level(output),
|
||||
FileBuilderEnum::RustFileBuilder(builder) => builder.write_top_level(output),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_namespace(&mut self, output: &mut dyn Write, name: &str) -> Result<()> {
|
||||
match self {
|
||||
FileBuilderEnum::CppBuilder(builder) => builder.write_namespace(output, name),
|
||||
FileBuilderEnum::CSharpBuilder(builder) => builder.write_namespace(output, name),
|
||||
FileBuilderEnum::JsonFileBuilder(builder) => builder.write_namespace(output, name),
|
||||
FileBuilderEnum::RustFileBuilder(builder) => builder.write_namespace(output, name),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_variable(&mut self, output: &mut dyn Write, name: &str, value: usize) -> Result<()> {
|
||||
match self {
|
||||
FileBuilderEnum::CppBuilder(builder) => builder.write_variable(output, name, value),
|
||||
FileBuilderEnum::CSharpBuilder(builder) => builder.write_variable(output, name, value),
|
||||
FileBuilderEnum::JsonFileBuilder(builder) => {
|
||||
builder.write_variable(output, name, value)
|
||||
}
|
||||
FileBuilderEnum::RustFileBuilder(builder) => {
|
||||
builder.write_variable(output, name, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_closure(&mut self, output: &mut dyn Write, eof: bool) -> Result<()> {
|
||||
match self {
|
||||
FileBuilderEnum::CppBuilder(builder) => builder.write_closure(output, eof),
|
||||
FileBuilderEnum::CSharpBuilder(builder) => builder.write_closure(output, eof),
|
||||
FileBuilderEnum::JsonFileBuilder(builder) => builder.write_closure(output, eof),
|
||||
FileBuilderEnum::RustFileBuilder(builder) => builder.write_closure(output, eof),
|
||||
}
|
||||
}
|
||||
}
|
38
src/builder/rust_file_builder.rs
Normal file
38
src/builder/rust_file_builder.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
use std::io::{Result, Write};
|
||||
|
||||
use super::FileBuilder;
|
||||
|
||||
pub struct RustFileBuilder;
|
||||
|
||||
impl FileBuilder for RustFileBuilder {
|
||||
fn extension(&mut self) -> &str {
|
||||
"rs"
|
||||
}
|
||||
|
||||
fn write_top_level(&mut self, output: &mut dyn Write) -> Result<()> {
|
||||
write!(
|
||||
output,
|
||||
"#![allow(non_snake_case, non_upper_case_globals)]\n\n"
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_namespace(&mut self, output: &mut dyn Write, name: &str) -> Result<()> {
|
||||
write!(output, "pub mod {} {{\n", name)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_variable(&mut self, output: &mut dyn Write, name: &str, value: usize) -> Result<()> {
|
||||
write!(output, " pub const {}: usize = {:#X};\n", name, value)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_closure(&mut self, output: &mut dyn Write, eof: bool) -> Result<()> {
|
||||
write!(output, "{}", if eof { "}" } else { "}\n\n" })?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
16
src/config.rs
Normal file
16
src/config.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Signature {
|
||||
pub name: String,
|
||||
pub module: String,
|
||||
pub pattern: String,
|
||||
pub relative: bool,
|
||||
pub levels: i32,
|
||||
pub offset: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Config {
|
||||
pub signatures: Vec<Signature>,
|
||||
}
|
54
src/dumpers/interfaces.rs
Normal file
54
src/dumpers/interfaces.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use crate::builder::FileBuilderEnum;
|
||||
use crate::error::Result;
|
||||
use crate::remote::Process;
|
||||
|
||||
use super::{generate_file, Entries};
|
||||
|
||||
pub fn dump_interfaces(builders: &mut Vec<FileBuilderEnum>, process: &Process) -> Result<()> {
|
||||
let module_names = process.get_loaded_modules()?;
|
||||
|
||||
let mut entries = Entries::new();
|
||||
|
||||
for module_name in module_names {
|
||||
let module = process.get_module_by_name(&module_name)?;
|
||||
|
||||
log::info!("Dumping interfaces in {}...", module_name);
|
||||
|
||||
if let Some(create_interface_export) = module.export("CreateInterface") {
|
||||
let create_interface_address = process.resolve_relative(create_interface_export.va)?;
|
||||
|
||||
let mut interface_registry = process
|
||||
.read_memory::<usize>(create_interface_address)
|
||||
.unwrap_or(0);
|
||||
|
||||
while interface_registry != 0 {
|
||||
let interface_ptr = process.read_memory::<usize>(interface_registry)?;
|
||||
|
||||
let interface_version_name_ptr =
|
||||
process.read_memory::<usize>(interface_registry + 0x8)?;
|
||||
|
||||
let interface_version_name = process.read_string(interface_version_name_ptr, 64)?;
|
||||
|
||||
log::info!(
|
||||
" -> Found '{}' @ {:#X} (RVA: {:#X})",
|
||||
interface_version_name,
|
||||
interface_ptr,
|
||||
interface_ptr - module.address()
|
||||
);
|
||||
|
||||
entries
|
||||
.entry(module_name.replace(".", "_"))
|
||||
.or_default()
|
||||
.push((interface_version_name, interface_ptr - module.address()));
|
||||
|
||||
interface_registry = process.read_memory::<usize>(interface_registry + 0x10)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for builder in builders.iter_mut() {
|
||||
generate_file(builder, "interfaces", &entries)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
41
src/dumpers/mod.rs
Normal file
41
src/dumpers/mod.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs::File;
|
||||
|
||||
use crate::builder::{FileBuilder, FileBuilderEnum};
|
||||
use crate::error::Result;
|
||||
|
||||
pub use interfaces::dump_interfaces;
|
||||
pub use offsets::dump_offsets;
|
||||
pub use schemas::dump_schemas;
|
||||
|
||||
pub mod interfaces;
|
||||
pub mod offsets;
|
||||
pub mod schemas;
|
||||
|
||||
pub type Entries = BTreeMap<String, Vec<(String, usize)>>;
|
||||
|
||||
pub fn generate_file(
|
||||
builder: &mut FileBuilderEnum,
|
||||
file_name: &str,
|
||||
entries: &Entries,
|
||||
) -> Result<()> {
|
||||
let file_path = format!("generated/{}.{}", file_name, builder.extension());
|
||||
|
||||
let mut file = File::create(file_path)?;
|
||||
|
||||
builder.write_top_level(&mut file)?;
|
||||
|
||||
let len = entries.len();
|
||||
|
||||
for (i, entry) in entries.iter().enumerate() {
|
||||
builder.write_namespace(&mut file, entry.0)?;
|
||||
|
||||
for (name, value) in entry.1 {
|
||||
builder.write_variable(&mut file, name, *value)?;
|
||||
}
|
||||
|
||||
builder.write_closure(&mut file, i == len - 1)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
54
src/dumpers/offsets.rs
Normal file
54
src/dumpers/offsets.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use std::fs::File;
|
||||
|
||||
use crate::builder::FileBuilderEnum;
|
||||
use crate::config::Config;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::remote::Process;
|
||||
|
||||
use super::{generate_file, Entries};
|
||||
|
||||
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).map_err(Error::SerdeError)?;
|
||||
|
||||
let mut entries = Entries::new();
|
||||
|
||||
log::info!("Dumping offsets...");
|
||||
|
||||
for signature in config.signatures {
|
||||
let module = process.get_module_by_name(&signature.module)?;
|
||||
|
||||
let mut address = process.find_pattern(&signature.module, &signature.pattern)?;
|
||||
|
||||
if signature.relative {
|
||||
address = process.resolve_relative(address)?;
|
||||
}
|
||||
|
||||
if signature.levels > 0 {
|
||||
for _ in 0..signature.levels {
|
||||
address = process.read_memory::<usize>(address)?;
|
||||
}
|
||||
}
|
||||
|
||||
address += signature.offset as usize;
|
||||
|
||||
log::info!(
|
||||
" -> Found '{}' @ {:#X} (RVA: {:#X})",
|
||||
signature.name,
|
||||
address,
|
||||
address - module.address()
|
||||
);
|
||||
|
||||
entries
|
||||
.entry(signature.module.replace(".", "_"))
|
||||
.or_default()
|
||||
.push((signature.name, address - module.address()));
|
||||
}
|
||||
|
||||
for builder in builders.iter_mut() {
|
||||
generate_file(builder, "offsets", &entries)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
39
src/dumpers/schemas.rs
Normal file
39
src/dumpers/schemas.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use crate::builder::FileBuilderEnum;
|
||||
use crate::error::Result;
|
||||
use crate::remote::Process;
|
||||
use crate::sdk::SchemaSystem;
|
||||
|
||||
use super::{generate_file, 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()? {
|
||||
log::info!("Generating files for {}...", type_scope.module_name()?);
|
||||
|
||||
let mut entries = Entries::new();
|
||||
|
||||
for class in type_scope.classes()? {
|
||||
log::info!(" [{}]", class.name());
|
||||
|
||||
for field in class.fields()? {
|
||||
log::info!(" [{}] = {:#X}", field.name()?, field.offset()?);
|
||||
|
||||
entries
|
||||
.entry(class.name().replace("::", "_"))
|
||||
.or_default()
|
||||
.push((field.name()?, field.offset()? as usize));
|
||||
}
|
||||
}
|
||||
|
||||
if entries.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
for builder in builders.iter_mut() {
|
||||
generate_file(builder, &type_scope.module_name()?, &entries)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
48
src/error.rs
Normal file
48
src/error.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use std::io;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
InvalidMagic(u32),
|
||||
IOError(io::Error),
|
||||
ModuleNotFound,
|
||||
PatternNotFound,
|
||||
ProcessNotFound,
|
||||
SerdeError(serde_json::Error),
|
||||
Utf8Error(std::string::FromUtf8Error),
|
||||
WindowsError(windows::core::Error),
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(err: io::Error) -> Self {
|
||||
Self::IOError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::string::FromUtf8Error> for Error {
|
||||
fn from(err: std::string::FromUtf8Error) -> Self {
|
||||
Self::Utf8Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<windows::core::Error> for Error {
|
||||
fn from(err: windows::core::Error) -> Self {
|
||||
Self::WindowsError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::InvalidMagic(magic) => write!(fmt, "Invalid magic: {:#X}", magic),
|
||||
Self::IOError(err) => write!(fmt, "IO error: {}", err),
|
||||
Self::ModuleNotFound => write!(fmt, "Module not found"),
|
||||
Self::PatternNotFound => write!(fmt, "Pattern not found"),
|
||||
Self::ProcessNotFound => write!(fmt, "Process not found"),
|
||||
Self::SerdeError(err) => write!(fmt, "Serde error: {}", err),
|
||||
Self::Utf8Error(err) => write!(fmt, "UTF-8 error: {}", err),
|
||||
Self::WindowsError(err) => write!(fmt, "Windows error: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
233
src/main.cpp
233
src/main.cpp
@@ -1,233 +0,0 @@
|
||||
#include "builder/builder.hpp"
|
||||
#include "process.hpp"
|
||||
#include "sdk/sdk.hpp"
|
||||
#include "utility/string.hpp"
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <regex>
|
||||
|
||||
using Entries = std::map<std::string, std::vector<std::pair<std::string, std::uint64_t>>, std::less<>>;
|
||||
|
||||
struct Signature {
|
||||
std::string name;
|
||||
std::string module;
|
||||
std::string pattern;
|
||||
bool relative;
|
||||
std::uint32_t levels;
|
||||
std::ptrdiff_t offset;
|
||||
};
|
||||
|
||||
static const std::array<std::pair<std::string_view, std::unique_ptr<builder::IFileBuilder>>, 4> builders = {
|
||||
{
|
||||
{ "cs", std::make_unique<builder::CSharpFileBuilder>() },
|
||||
{ "hpp", std::make_unique<builder::CppFileBuilder>() },
|
||||
{ "json", std::make_unique<builder::JsonFileBuilder>() },
|
||||
{ "rs", std::make_unique<builder::RustFileBuilder>() }
|
||||
}
|
||||
};
|
||||
|
||||
std::string sanitize_module_name(const std::string& name) {
|
||||
static std::regex double_colon_pattern("\\.");
|
||||
|
||||
return std::regex_replace(name, double_colon_pattern, "_");
|
||||
}
|
||||
|
||||
template <class IFileBuilder>
|
||||
void generate_file(const std::string_view file_name, const Entries& entries, IFileBuilder& builder) {
|
||||
const std::string output_file_path = std::format("generated/{}.{}", file_name, builder.extension());
|
||||
|
||||
std::ofstream file(output_file_path);
|
||||
|
||||
if (!file.good()) {
|
||||
spdlog::error("Failed to open {}.", file_name);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const auto sanitize_namespace_name = [](const std::string& namespace_name) {
|
||||
static std::regex double_colon_pattern("\\::");
|
||||
|
||||
return std::regex_replace(namespace_name, double_colon_pattern, "_");
|
||||
};
|
||||
|
||||
builder.write_top_level(file);
|
||||
|
||||
for (auto it = entries.begin(); it != entries.end(); ++it) {
|
||||
const auto& [namespace_name, variables] = *it;
|
||||
|
||||
const std::string sanitized_namespace = sanitize_namespace_name(namespace_name);
|
||||
|
||||
builder.write_namespace(file, sanitized_namespace);
|
||||
|
||||
for (const auto& [variable_name, variable_value] : variables)
|
||||
builder.write_variable(file, variable_name, variable_value);
|
||||
|
||||
builder.write_closure(file, it == std::prev(entries.end()));
|
||||
}
|
||||
}
|
||||
|
||||
void dump_interfaces() {
|
||||
const auto loaded_modules = process::loaded_modules();
|
||||
|
||||
if (!loaded_modules.has_value()) {
|
||||
spdlog::critical("Failed to get loaded modules.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
spdlog::info("Generating interface files...");
|
||||
|
||||
Entries entries;
|
||||
|
||||
for (const auto& module_name : loaded_modules.value()) {
|
||||
const auto module_base = process::get_module_base_by_name(module_name);
|
||||
|
||||
if (!module_base.has_value())
|
||||
continue;
|
||||
|
||||
const auto create_interface_address = process::get_module_export_by_name(module_base.value(), "CreateInterface");
|
||||
|
||||
if (!create_interface_address.has_value())
|
||||
continue;
|
||||
|
||||
auto interface_registry = utility::Address(create_interface_address.value()).rip().get();
|
||||
|
||||
if (!interface_registry.is_valid())
|
||||
continue;
|
||||
|
||||
while (interface_registry.is_valid()) {
|
||||
const std::uint64_t interface_ptr = interface_registry.get().address();
|
||||
|
||||
const std::string interface_version_name = process::read_string(interface_registry.add(0x8).get().address(), 64);
|
||||
|
||||
entries[sanitize_module_name(module_name)].emplace_back(interface_version_name, interface_ptr - module_base.value());
|
||||
|
||||
interface_registry = interface_registry.add(0x10).get();
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [extension, builder] : builders) {
|
||||
generate_file("interfaces", entries, *builder);
|
||||
|
||||
spdlog::info(" > Generated {}.{}", "interfaces", extension);
|
||||
}
|
||||
}
|
||||
|
||||
void dump_offsets() {
|
||||
std::ifstream file("config.json");
|
||||
|
||||
if (!file.good()) {
|
||||
spdlog::critical("Failed to open config.json.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const auto json = nlohmann::json::parse(file);
|
||||
|
||||
Entries entries;
|
||||
|
||||
for (const auto& element : json["signatures"]) {
|
||||
const Signature signature = {
|
||||
.name = element["name"],
|
||||
.module = element["module"],
|
||||
.pattern = element["pattern"],
|
||||
.relative = element["relative"],
|
||||
.levels = element["levels"],
|
||||
.offset = element["offset"]
|
||||
};
|
||||
|
||||
const auto module_base = process::get_module_base_by_name(signature.module);
|
||||
|
||||
if (!module_base.has_value())
|
||||
continue;
|
||||
|
||||
auto address = process::find_pattern(signature.module, signature.pattern);
|
||||
|
||||
if (!address.has_value())
|
||||
continue;
|
||||
|
||||
if (signature.relative)
|
||||
address = address->rip();
|
||||
|
||||
if (signature.levels > 0)
|
||||
address = address->get(signature.levels);
|
||||
|
||||
address = address->add(signature.offset);
|
||||
|
||||
spdlog::info("Found '{}' @ {:#x} (RVA: {:#x})", signature.name, address->address(), address->address() - module_base.value());
|
||||
|
||||
entries[sanitize_module_name(signature.module)].emplace_back(signature.name, address->address() - module_base.value());
|
||||
}
|
||||
|
||||
spdlog::info("Generating offset files...");
|
||||
|
||||
for (const auto& [extension, builder] : builders) {
|
||||
generate_file("offsets", entries, *builder);
|
||||
|
||||
spdlog::info(" > Generated file: {}.{}", "offsets", extension);
|
||||
}
|
||||
} catch (const nlohmann::json::parse_error& ex) {
|
||||
spdlog::critical("Failed to parse config.json: {}", ex.what());
|
||||
}
|
||||
}
|
||||
|
||||
void dump_schemas() {
|
||||
const auto schema_system = sdk::SchemaSystem::get();
|
||||
|
||||
if (schema_system == nullptr) {
|
||||
spdlog::critical("Failed to get schema system.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
spdlog::info("Schema system: {:#x}", reinterpret_cast<std::uint64_t>(schema_system));
|
||||
|
||||
for (const auto& type_scope : schema_system->type_scopes()) {
|
||||
const std::string module_name = type_scope->module_name();
|
||||
|
||||
spdlog::info("Generating files for {}...", module_name);
|
||||
|
||||
Entries entries;
|
||||
|
||||
type_scope->for_each_class([&entries](const std::pair<std::string, sdk::SchemaClassInfo*>& pair) {
|
||||
spdlog::info(" [{}] @ {:#x}", pair.first, reinterpret_cast<std::uint64_t>(pair.second));
|
||||
|
||||
pair.second->for_each_field([&entries, &pair](const sdk::SchemaClassFieldData* field) {
|
||||
spdlog::info(" [{}] = {:#x}", field->name(), field->offset());
|
||||
|
||||
entries[pair.first].emplace_back(field->name(), field->offset());
|
||||
});
|
||||
});
|
||||
|
||||
for (const auto& [extension, builder] : builders) {
|
||||
generate_file(module_name, entries, *builder);
|
||||
|
||||
spdlog::info(" > Generated file: {}.{}", module_name, extension);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
const auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
if (!std::filesystem::exists("generated"))
|
||||
std::filesystem::create_directory("generated");
|
||||
|
||||
if (!process::attach("cs2.exe")) {
|
||||
spdlog::critical("Failed to attach to cs2.exe.");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
dump_interfaces();
|
||||
|
||||
dump_offsets();
|
||||
|
||||
dump_schemas();
|
||||
|
||||
spdlog::info("Done! Took {}ms.", std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start).count());
|
||||
|
||||
return 0;
|
||||
}
|
45
src/main.rs
Normal file
45
src/main.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::fs;
|
||||
use std::time::Instant;
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
|
||||
use builder::*;
|
||||
use dumpers::*;
|
||||
use error::Result;
|
||||
use remote::Process;
|
||||
|
||||
mod builder;
|
||||
mod config;
|
||||
mod dumpers;
|
||||
mod error;
|
||||
mod remote;
|
||||
mod sdk;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
|
||||
fs::create_dir_all("generated")?;
|
||||
|
||||
let process = Process::new("cs2.exe")?;
|
||||
|
||||
let mut builders: Vec<FileBuilderEnum> = vec![
|
||||
FileBuilderEnum::CppBuilder(CppBuilder),
|
||||
FileBuilderEnum::CSharpBuilder(CSharpBuilder),
|
||||
FileBuilderEnum::JsonFileBuilder(JsonFileBuilder::default()),
|
||||
FileBuilderEnum::RustFileBuilder(RustFileBuilder),
|
||||
];
|
||||
|
||||
let start_time = Instant::now();
|
||||
|
||||
dump_schemas(&mut builders, &process)?;
|
||||
dump_interfaces(&mut builders, &process)?;
|
||||
dump_offsets(&mut builders, &process)?;
|
||||
|
||||
let duration = start_time.elapsed();
|
||||
|
||||
log::info!("Done! Time elapsed: {:?}", duration);
|
||||
|
||||
Ok(())
|
||||
}
|
238
src/process.cpp
238
src/process.cpp
@@ -1,238 +0,0 @@
|
||||
#include "process.hpp"
|
||||
#include "utility/safe_handle.hpp"
|
||||
#include "utility/string.hpp"
|
||||
|
||||
#include <charconv>
|
||||
#include <vector>
|
||||
|
||||
#include <Windows.h>
|
||||
#include <TlHelp32.h>
|
||||
|
||||
namespace process {
|
||||
std::uint32_t process_id = 0;
|
||||
|
||||
utility::SafeHandle process_handle;
|
||||
|
||||
std::optional<std::uint32_t> get_process_id_by_name(const std::string_view process_name) noexcept {
|
||||
const utility::SafeHandle snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0));
|
||||
|
||||
if (snapshot.get() == INVALID_HANDLE_VALUE)
|
||||
return std::nullopt;
|
||||
|
||||
PROCESSENTRY32 process_entry = {
|
||||
.dwSize = sizeof(PROCESSENTRY32)
|
||||
};
|
||||
|
||||
for (Process32First(snapshot.get(), &process_entry); Process32Next(snapshot.get(), &process_entry);) {
|
||||
if (std::string_view(process_entry.szExeFile) == process_name)
|
||||
return process_entry.th32ProcessID;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool attach(const std::string_view process_name) noexcept {
|
||||
const auto id = get_process_id_by_name(process_name);
|
||||
|
||||
if (!id.has_value())
|
||||
return false;
|
||||
|
||||
process_id = id.value();
|
||||
|
||||
process_handle = utility::SafeHandle(OpenProcess(PROCESS_ALL_ACCESS, 0, process_id));
|
||||
|
||||
return process_handle.get() != nullptr;
|
||||
}
|
||||
|
||||
std::optional<utility::Address> find_pattern(const std::string_view module_name, const std::string_view pattern) noexcept {
|
||||
constexpr auto pattern_to_bytes = [](const std::string_view pattern) {
|
||||
std::vector<std::int32_t> bytes;
|
||||
|
||||
for (std::size_t i = 0; i < pattern.size(); ++i) {
|
||||
switch (pattern[i]) {
|
||||
case '?':
|
||||
bytes.push_back(-1);
|
||||
break;
|
||||
|
||||
case ' ':
|
||||
break;
|
||||
|
||||
default: {
|
||||
if (i + 1 < pattern.size()) {
|
||||
std::int32_t value = 0;
|
||||
|
||||
if (const auto [ptr, ec] = std::from_chars(pattern.data() + i, pattern.data() + i + 2, value, 16); ec == std::errc()) {
|
||||
bytes.push_back(value);
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bytes;
|
||||
};
|
||||
|
||||
const auto module_base = get_module_base_by_name(module_name);
|
||||
|
||||
if (!module_base.has_value())
|
||||
return std::nullopt;
|
||||
|
||||
const auto headers = std::make_unique<std::uint8_t[]>(0x1000);
|
||||
|
||||
if (!read_memory(module_base.value(), headers.get(), 0x1000))
|
||||
return std::nullopt;
|
||||
|
||||
const auto dos_header = reinterpret_cast<PIMAGE_DOS_HEADER>(headers.get());
|
||||
|
||||
if (dos_header->e_magic != IMAGE_DOS_SIGNATURE)
|
||||
return std::nullopt;
|
||||
|
||||
const auto nt_headers = reinterpret_cast<PIMAGE_NT_HEADERS>(headers.get() + dos_header->e_lfanew);
|
||||
|
||||
if (nt_headers->Signature != IMAGE_NT_SIGNATURE)
|
||||
return std::nullopt;
|
||||
|
||||
const std::uint32_t module_size = nt_headers->OptionalHeader.SizeOfImage;
|
||||
|
||||
const auto module_data = std::make_unique<std::uint8_t[]>(module_size);
|
||||
|
||||
if (!read_memory(module_base.value(), module_data.get(), module_size))
|
||||
return std::nullopt;
|
||||
|
||||
const auto pattern_bytes = pattern_to_bytes(pattern);
|
||||
|
||||
for (std::size_t i = 0; i < module_size - pattern.size(); ++i) {
|
||||
bool found = true;
|
||||
|
||||
for (std::size_t j = 0; j < pattern_bytes.size(); ++j) {
|
||||
if (module_data[i + j] != pattern_bytes[j] && pattern_bytes[j] != -1) {
|
||||
found = false;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found)
|
||||
return utility::Address(module_base.value() + i);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::uintptr_t> get_module_base_by_name(const std::string_view module_name) noexcept {
|
||||
const utility::SafeHandle snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, process_id));
|
||||
|
||||
if (snapshot.get() == INVALID_HANDLE_VALUE)
|
||||
return std::nullopt;
|
||||
|
||||
MODULEENTRY32 module_entry = {
|
||||
.dwSize = sizeof(MODULEENTRY32)
|
||||
};
|
||||
|
||||
for (Module32First(snapshot.get(), &module_entry); Module32Next(snapshot.get(), &module_entry);) {
|
||||
if (utility::string::equals_ignore_case(module_entry.szModule, module_name))
|
||||
return reinterpret_cast<std::uintptr_t>(module_entry.modBaseAddr);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::uintptr_t> get_module_export_by_name(const std::uintptr_t module_base, const std::string_view function_name) noexcept {
|
||||
const auto headers = std::make_unique<std::uint8_t[]>(0x1000);
|
||||
|
||||
if (!read_memory(module_base, headers.get(), 0x1000))
|
||||
return std::nullopt;
|
||||
|
||||
const auto dos_header = reinterpret_cast<PIMAGE_DOS_HEADER>(headers.get());
|
||||
|
||||
if (dos_header->e_magic != IMAGE_DOS_SIGNATURE)
|
||||
return std::nullopt;
|
||||
|
||||
const auto nt_headers = reinterpret_cast<PIMAGE_NT_HEADERS>(headers.get() + dos_header->e_lfanew);
|
||||
|
||||
if (nt_headers->Signature != IMAGE_NT_SIGNATURE)
|
||||
return std::nullopt;
|
||||
|
||||
const IMAGE_DATA_DIRECTORY export_data_directory = nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
|
||||
|
||||
if (export_data_directory.VirtualAddress == 0 || export_data_directory.Size == 0)
|
||||
return std::nullopt;
|
||||
|
||||
const auto export_directory_buffer = std::make_unique<std::uint8_t[]>(export_data_directory.Size);
|
||||
|
||||
if (!read_memory(module_base + export_data_directory.VirtualAddress, export_directory_buffer.get(), export_data_directory.Size))
|
||||
return std::nullopt;
|
||||
|
||||
const auto export_directory = reinterpret_cast<PIMAGE_EXPORT_DIRECTORY>(export_directory_buffer.get());
|
||||
|
||||
const std::uintptr_t delta = reinterpret_cast<std::uintptr_t>(export_directory) - export_data_directory.VirtualAddress;
|
||||
|
||||
const auto name_table = reinterpret_cast<std::uint32_t*>(export_directory->AddressOfNames + delta);
|
||||
const auto name_ordinal_table = reinterpret_cast<std::uint16_t*>(export_directory->AddressOfNameOrdinals + delta);
|
||||
const auto function_table = reinterpret_cast<std::uint32_t*>(export_directory->AddressOfFunctions + delta);
|
||||
|
||||
for (std::size_t i = 0; i < export_directory->NumberOfNames; ++i) {
|
||||
const std::string_view current_function_name = reinterpret_cast<const char*>(name_table[i] + delta);
|
||||
|
||||
if (current_function_name != function_name)
|
||||
continue;
|
||||
|
||||
const std::uint16_t function_ordinal = name_ordinal_table[i];
|
||||
const std::uintptr_t function_address = module_base + function_table[function_ordinal];
|
||||
|
||||
if (function_address >= module_base + export_data_directory.VirtualAddress &&
|
||||
function_address <= module_base + export_data_directory.VirtualAddress + export_data_directory.Size
|
||||
) {
|
||||
// TODO: Handle forwarded exports.
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return function_address;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::vector<std::string>> loaded_modules() noexcept {
|
||||
const utility::SafeHandle snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, process_id));
|
||||
|
||||
if (snapshot.get() == INVALID_HANDLE_VALUE)
|
||||
return std::nullopt;
|
||||
|
||||
MODULEENTRY32 module_entry = {
|
||||
.dwSize = sizeof(MODULEENTRY32)
|
||||
};
|
||||
|
||||
std::vector<std::string> loaded_modules;
|
||||
|
||||
for (Module32First(snapshot.get(), &module_entry); Module32Next(snapshot.get(), &module_entry);)
|
||||
loaded_modules.emplace_back(module_entry.szModule);
|
||||
|
||||
return loaded_modules;
|
||||
}
|
||||
|
||||
bool read_memory(const std::uintptr_t address, void* buffer, const std::size_t size) noexcept {
|
||||
return ReadProcessMemory(process_handle.get(), reinterpret_cast<void*>(address), buffer, size, nullptr);
|
||||
}
|
||||
|
||||
bool write_memory(const std::uintptr_t address, const void* buffer, const std::size_t size) noexcept {
|
||||
return WriteProcessMemory(process_handle.get(), reinterpret_cast<void*>(address), buffer, size, nullptr);
|
||||
}
|
||||
|
||||
std::string read_string(const std::uintptr_t address, const std::size_t length) noexcept {
|
||||
std::string buffer(length, '\0');
|
||||
|
||||
if (!read_memory(address, buffer.data(), length))
|
||||
return {};
|
||||
|
||||
if (const auto it = std::ranges::find(buffer, '\0'); it != buffer.end())
|
||||
buffer.erase(it, buffer.end());
|
||||
|
||||
return buffer;
|
||||
}
|
||||
}
|
5
src/remote/mod.rs
Normal file
5
src/remote/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
pub use module::Module;
|
||||
pub use process::Process;
|
||||
|
||||
pub mod module;
|
||||
pub mod process;
|
179
src/remote/module.rs
Normal file
179
src/remote/module.rs
Normal file
@@ -0,0 +1,179 @@
|
||||
use std::ffi::CStr;
|
||||
use std::slice;
|
||||
|
||||
use windows::Win32::System::Diagnostics::Debug::*;
|
||||
use windows::Win32::System::SystemServices::*;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
|
||||
use super::Process;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Export {
|
||||
pub name: String,
|
||||
pub va: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Section {
|
||||
pub name: String,
|
||||
pub start_rva: usize,
|
||||
pub end_rva: usize,
|
||||
pub size: usize,
|
||||
}
|
||||
|
||||
pub struct Module<'a> {
|
||||
address: usize,
|
||||
nt_headers: &'a IMAGE_NT_HEADERS64,
|
||||
exports: Vec<Export>,
|
||||
sections: Vec<Section>,
|
||||
}
|
||||
|
||||
impl<'a> Module<'a> {
|
||||
pub fn new(process: &'a Process, address: usize) -> Result<Self> {
|
||||
let mut headers: [u8; 0x1000] = [0; 0x1000];
|
||||
|
||||
process.read_memory_raw(address, headers.as_mut_ptr() as *mut _, headers.len())?;
|
||||
|
||||
let dos_header = unsafe { &*(headers.as_ptr() as *const IMAGE_DOS_HEADER) };
|
||||
|
||||
if dos_header.e_magic != IMAGE_DOS_SIGNATURE {
|
||||
return Err(Error::InvalidMagic(dos_header.e_magic as u32));
|
||||
}
|
||||
|
||||
let nt_headers = unsafe {
|
||||
&*(headers.as_ptr().offset(dos_header.e_lfanew as isize) as *const IMAGE_NT_HEADERS64)
|
||||
};
|
||||
|
||||
if nt_headers.Signature != IMAGE_NT_SIGNATURE {
|
||||
return Err(Error::InvalidMagic(nt_headers.Signature));
|
||||
}
|
||||
|
||||
let exports = unsafe { Self::parse_exports(process, address, nt_headers)? };
|
||||
let sections = unsafe { Self::parse_sections(nt_headers) };
|
||||
|
||||
Ok(Self {
|
||||
address,
|
||||
nt_headers,
|
||||
exports,
|
||||
sections,
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn address(&self) -> usize {
|
||||
self.address
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn exports(&self) -> &Vec<Export> {
|
||||
&self.exports
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sections(&self) -> &Vec<Section> {
|
||||
&self.sections
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn export(&self, name: &str) -> Option<&Export> {
|
||||
self.exports.iter().find(|export| export.name == name)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn section(&self, name: &str) -> Option<&Section> {
|
||||
self.sections.iter().find(|section| section.name == name)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn size(&self) -> u32 {
|
||||
self.nt_headers.OptionalHeader.SizeOfImage
|
||||
}
|
||||
|
||||
unsafe fn parse_exports(
|
||||
process: &Process,
|
||||
address: usize,
|
||||
nt_headers: &IMAGE_NT_HEADERS64,
|
||||
) -> Result<Vec<Export>> {
|
||||
let export_data_directory =
|
||||
nt_headers.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT.0 as usize];
|
||||
|
||||
let export_directory_start = address + export_data_directory.VirtualAddress as usize;
|
||||
let export_directory_end = export_directory_start + export_data_directory.Size as usize;
|
||||
|
||||
let mut buffer: Vec<u8> = vec![0; export_data_directory.Size as usize];
|
||||
|
||||
process.read_memory_raw(
|
||||
export_directory_start,
|
||||
buffer.as_mut_ptr() as *mut _,
|
||||
buffer.len(),
|
||||
)?;
|
||||
|
||||
let export_directory = &*(buffer.as_ptr() as *const IMAGE_EXPORT_DIRECTORY);
|
||||
|
||||
let delta =
|
||||
export_directory as *const _ as usize - export_data_directory.VirtualAddress as usize;
|
||||
|
||||
let name_table = (delta + export_directory.AddressOfNames as usize) as *const u32;
|
||||
let ordinal_table = (delta + export_directory.AddressOfNameOrdinals as usize) as *const u16;
|
||||
let function_table = (delta + export_directory.AddressOfFunctions as usize) as *const u32;
|
||||
|
||||
let mut exports: Vec<Export> = Vec::with_capacity(export_directory.NumberOfNames as usize);
|
||||
|
||||
for i in 0..export_directory.NumberOfNames {
|
||||
let function_ordinal = *ordinal_table.offset(i as isize);
|
||||
let function_va = address + *function_table.offset(function_ordinal as isize) as usize;
|
||||
|
||||
// Skip forwarded exports.
|
||||
if function_va >= export_directory_start && function_va <= export_directory_end {
|
||||
continue;
|
||||
}
|
||||
|
||||
let name = CStr::from_ptr(
|
||||
delta.wrapping_add(*name_table.offset(i as isize) as usize) as *const i8
|
||||
)
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
|
||||
exports.push(Export {
|
||||
name,
|
||||
va: function_va,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(exports)
|
||||
}
|
||||
|
||||
unsafe fn parse_sections(nt_headers: &IMAGE_NT_HEADERS64) -> Vec<Section> {
|
||||
let optional_header_ptr = &nt_headers.OptionalHeader as *const _ as *const u8;
|
||||
|
||||
let section_header_ptr = optional_header_ptr
|
||||
.offset(nt_headers.FileHeader.SizeOfOptionalHeader as isize)
|
||||
as *const IMAGE_SECTION_HEADER;
|
||||
|
||||
let sections_raw = slice::from_raw_parts(
|
||||
section_header_ptr,
|
||||
nt_headers.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 size = section.SizeOfRawData as usize;
|
||||
|
||||
Section {
|
||||
name,
|
||||
start_rva,
|
||||
end_rva,
|
||||
size,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
255
src/remote/process.rs
Normal file
255
src/remote/process.rs
Normal file
@@ -0,0 +1,255 @@
|
||||
use std::ffi::{c_void, CStr};
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
|
||||
use windows::Win32::Foundation::*;
|
||||
use windows::Win32::System::Diagnostics::Debug::*;
|
||||
use windows::Win32::System::Diagnostics::ToolHelp::*;
|
||||
use windows::Win32::System::Threading::*;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
|
||||
use super::Module;
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn find_pattern(&self, module_name: &str, pattern: &str) -> Result<usize> {
|
||||
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.address(),
|
||||
module_data.as_mut_ptr() as *mut _,
|
||||
module_data.len(),
|
||||
)?;
|
||||
|
||||
let pattern_bytes = Self::pattern_to_bytes(pattern);
|
||||
|
||||
for i in 0..module.size() as usize - pattern.len() {
|
||||
let mut found = true;
|
||||
|
||||
for j in 0..pattern_bytes.len() {
|
||||
if module_data[i + j] != pattern_bytes[j] as u8 && pattern_bytes[j] != -1 {
|
||||
found = false;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
return Ok(module.address() + i);
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::PatternNotFound)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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, entry.modBaseAddr as usize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::ModuleNotFound)
|
||||
}
|
||||
|
||||
pub fn read_memory_raw(&self, address: usize, buffer: *mut c_void, size: usize) -> Result<()> {
|
||||
unsafe {
|
||||
ReadProcessMemory(
|
||||
self.process_handle,
|
||||
address as *const _,
|
||||
buffer,
|
||||
size,
|
||||
Some(ptr::null_mut()),
|
||||
)
|
||||
}
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn write_memory_raw(
|
||||
&self,
|
||||
address: usize,
|
||||
buffer: *const c_void,
|
||||
size: usize,
|
||||
) -> Result<()> {
|
||||
unsafe {
|
||||
WriteProcessMemory(
|
||||
self.process_handle,
|
||||
address as *const _,
|
||||
buffer,
|
||||
size,
|
||||
Some(ptr::null_mut()),
|
||||
)
|
||||
}
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn read_memory<T>(&self, address: usize) -> 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)
|
||||
}
|
||||
|
||||
pub fn write_memory<T>(&self, address: usize, value: T) -> Result<()> {
|
||||
self.write_memory_raw(address, &value as *const _ as *const _, mem::size_of::<T>())
|
||||
}
|
||||
|
||||
pub fn read_string(&self, address: usize, length: usize) -> Result<String> {
|
||||
let mut buffer: Vec<u8> = 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)?)
|
||||
}
|
||||
|
||||
pub fn resolve_jmp(&self, address: usize) -> Result<usize> {
|
||||
let displacement = self.read_memory::<i32>(address + 0x1)?;
|
||||
|
||||
Ok((address + 0x5) + displacement as usize)
|
||||
}
|
||||
|
||||
pub fn resolve_relative(&self, address: usize) -> Result<usize> {
|
||||
let displacement = self.read_memory::<i32>(address + 0x3)?;
|
||||
|
||||
Ok((address + 0x7) + displacement as usize)
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::ProcessNotFound)
|
||||
}
|
||||
|
||||
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() {
|
||||
let value = i32::from_str_radix(&pattern[i..i + 2], 16);
|
||||
|
||||
match value {
|
||||
Ok(v) => bytes.push(v),
|
||||
Err(_) => {}
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Process {
|
||||
fn drop(&mut self) {
|
||||
if !self.process_handle.is_invalid() {
|
||||
unsafe { CloseHandle(self.process_handle).unwrap() }
|
||||
}
|
||||
}
|
||||
}
|
13
src/sdk/mod.rs
Normal file
13
src/sdk/mod.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
pub use schema_class_field_data::SchemaClassFieldData;
|
||||
pub use schema_class_info::SchemaClassInfo;
|
||||
pub use schema_system::SchemaSystem;
|
||||
pub use schema_system_type_scope::SchemaSystemTypeScope;
|
||||
pub use schema_type_declared_class::SchemaTypeDeclaredClass;
|
||||
pub use utl_ts_hash::UtlTsHash;
|
||||
|
||||
pub mod schema_class_field_data;
|
||||
pub mod schema_class_info;
|
||||
pub mod schema_system;
|
||||
pub mod schema_system_type_scope;
|
||||
pub mod schema_type_declared_class;
|
||||
pub mod utl_ts_hash;
|
@@ -1,11 +0,0 @@
|
||||
#include "sdk/sdk.hpp"
|
||||
|
||||
namespace sdk {
|
||||
std::string SchemaClassFieldData::name() const noexcept {
|
||||
return process::read_string(process::read_memory<std::uint64_t>(reinterpret_cast<std::uint64_t>(this)), 64);
|
||||
}
|
||||
|
||||
std::uint16_t SchemaClassFieldData::offset() const noexcept {
|
||||
return process::read_memory<std::uint16_t>(reinterpret_cast<std::uint64_t>(this) + 0x10);
|
||||
}
|
||||
}
|
23
src/sdk/schema_class_field_data.rs
Normal file
23
src/sdk/schema_class_field_data.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use crate::error::Result;
|
||||
use crate::remote::Process;
|
||||
|
||||
pub struct SchemaClassFieldData<'a> {
|
||||
process: &'a Process,
|
||||
address: usize,
|
||||
}
|
||||
|
||||
impl<'a> SchemaClassFieldData<'a> {
|
||||
pub fn new(process: &'a Process, address: usize) -> Self {
|
||||
Self { process, address }
|
||||
}
|
||||
|
||||
pub fn name(&self) -> Result<String> {
|
||||
let name_ptr = self.process.read_memory::<usize>(self.address)?;
|
||||
|
||||
self.process.read_string(name_ptr, 64)
|
||||
}
|
||||
|
||||
pub fn offset(&self) -> Result<u16> {
|
||||
self.process.read_memory::<u16>(self.address + 0x10)
|
||||
}
|
||||
}
|
@@ -1,18 +0,0 @@
|
||||
#include "sdk/sdk.hpp"
|
||||
|
||||
namespace sdk {
|
||||
std::uint16_t SchemaClassInfo::fields_count() const noexcept {
|
||||
return process::read_memory<std::uint16_t>(reinterpret_cast<std::uint64_t>(this) + 0x1C);
|
||||
}
|
||||
|
||||
void SchemaClassInfo::for_each_field(const std::function<void(SchemaClassFieldData*)>& callback) const noexcept {
|
||||
for (std::size_t i = 0; i < fields_count(); ++i) {
|
||||
const auto field = reinterpret_cast<SchemaClassFieldData*>(process::read_memory<std::uint64_t>(reinterpret_cast<std::uint64_t>(this) + 0x28) + (i * 0x20));
|
||||
|
||||
if (field == nullptr)
|
||||
continue;
|
||||
|
||||
callback(field);
|
||||
}
|
||||
}
|
||||
}
|
47
src/sdk/schema_class_info.rs
Normal file
47
src/sdk/schema_class_info.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use crate::error::Result;
|
||||
use crate::remote::Process;
|
||||
|
||||
use super::SchemaClassFieldData;
|
||||
|
||||
pub struct SchemaClassInfo<'a> {
|
||||
process: &'a Process,
|
||||
address: usize,
|
||||
class_name: String,
|
||||
}
|
||||
|
||||
impl<'a> SchemaClassInfo<'a> {
|
||||
pub fn new(process: &'a Process, address: usize, class_name: &str) -> Self {
|
||||
Self {
|
||||
process,
|
||||
address,
|
||||
class_name: class_name.into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn name(&self) -> &str {
|
||||
&self.class_name
|
||||
}
|
||||
|
||||
pub fn fields(&self) -> Result<Vec<SchemaClassFieldData>> {
|
||||
let count = self.fields_count()?;
|
||||
|
||||
let fields: Vec<SchemaClassFieldData> = (0..count as usize)
|
||||
.filter_map(|i| {
|
||||
let field = self
|
||||
.process
|
||||
.read_memory::<usize>(self.address + 0x28)
|
||||
.ok()?
|
||||
+ (i * 0x20);
|
||||
|
||||
(field != 0).then(|| SchemaClassFieldData::new(self.process, field))
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(fields)
|
||||
}
|
||||
|
||||
pub fn fields_count(&self) -> Result<u16> {
|
||||
self.process.read_memory::<u16>(self.address + 0x1C)
|
||||
}
|
||||
}
|
@@ -1,28 +0,0 @@
|
||||
#include "sdk/sdk.hpp"
|
||||
|
||||
namespace sdk {
|
||||
SchemaSystem* SchemaSystem::get() noexcept {
|
||||
const auto 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");
|
||||
|
||||
if (!address.has_value())
|
||||
return nullptr;
|
||||
|
||||
return address->rip().as<SchemaSystem*>();
|
||||
}
|
||||
|
||||
std::vector<SchemaSystemTypeScope*> SchemaSystem::type_scopes() const noexcept {
|
||||
const auto count = process::read_memory<std::uint32_t>(reinterpret_cast<std::uint64_t>(this) + 0x190);
|
||||
const auto data = process::read_memory<std::uint64_t>(reinterpret_cast<std::uint64_t>(this) + 0x198);
|
||||
|
||||
if (count == 0 || data == 0)
|
||||
return {};
|
||||
|
||||
std::vector<sdk::SchemaSystemTypeScope*> type_scopes;
|
||||
|
||||
type_scopes.resize(count);
|
||||
|
||||
process::read_memory(data, type_scopes.data(), count * sizeof(std::uint64_t));
|
||||
|
||||
return type_scopes;
|
||||
}
|
||||
}
|
42
src/sdk/schema_system.rs
Normal file
42
src/sdk/schema_system.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use crate::error::Result;
|
||||
use crate::remote::Process;
|
||||
|
||||
use super::SchemaSystemTypeScope;
|
||||
|
||||
pub struct SchemaSystem<'a> {
|
||||
process: &'a Process,
|
||||
address: usize,
|
||||
}
|
||||
|
||||
impl<'a> SchemaSystem<'a> {
|
||||
pub fn new(process: &'a Process) -> Result<Self> {
|
||||
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"
|
||||
)?;
|
||||
|
||||
address = process.resolve_relative(address)?;
|
||||
|
||||
Ok(Self { process, address })
|
||||
}
|
||||
|
||||
pub fn type_scopes(&self) -> Result<Vec<SchemaSystemTypeScope>> {
|
||||
let size = self.process.read_memory::<u32>(self.address + 0x190)?;
|
||||
let data = self.process.read_memory::<usize>(self.address + 0x198)?;
|
||||
|
||||
let mut addresses: Vec<usize> = vec![0; size as usize];
|
||||
|
||||
self.process.read_memory_raw(
|
||||
data,
|
||||
addresses.as_mut_ptr() as *mut _,
|
||||
addresses.len() * std::mem::size_of::<usize>(),
|
||||
)?;
|
||||
|
||||
let type_scopes: Vec<SchemaSystemTypeScope> = addresses
|
||||
.iter()
|
||||
.map(|&address| SchemaSystemTypeScope::new(self.process, address))
|
||||
.collect();
|
||||
|
||||
Ok(type_scopes)
|
||||
}
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
#include "sdk/sdk.hpp"
|
||||
|
||||
namespace sdk {
|
||||
void SchemaSystemTypeScope::for_each_class(const std::function<void(std::pair<std::string, SchemaClassInfo*>)>& callback) const noexcept {
|
||||
const auto classes = process::read_memory<UtlTsHash<SchemaTypeDeclaredClass*>>(reinterpret_cast<std::uint64_t>(this) + 0x588);
|
||||
|
||||
for (const auto& element : classes.elements())
|
||||
callback({ element->binary_name(), reinterpret_cast<SchemaClassInfo*>(element) });
|
||||
}
|
||||
|
||||
std::string SchemaSystemTypeScope::module_name() const noexcept {
|
||||
return process::read_string(reinterpret_cast<std::uint64_t>(this) + 0x8, 256);
|
||||
}
|
||||
}
|
40
src/sdk/schema_system_type_scope.rs
Normal file
40
src/sdk/schema_system_type_scope.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use crate::error::Result;
|
||||
use crate::remote::Process;
|
||||
|
||||
use super::{SchemaClassInfo, SchemaTypeDeclaredClass, UtlTsHash};
|
||||
|
||||
pub struct SchemaSystemTypeScope<'a> {
|
||||
process: &'a Process,
|
||||
address: usize,
|
||||
}
|
||||
|
||||
impl<'a> SchemaSystemTypeScope<'a> {
|
||||
pub fn new(process: &'a Process, address: usize) -> Self {
|
||||
Self { process, address }
|
||||
}
|
||||
|
||||
pub fn classes(&self) -> Result<Vec<SchemaClassInfo>> {
|
||||
let classes = self
|
||||
.process
|
||||
.read_memory::<UtlTsHash<*mut SchemaTypeDeclaredClass>>(self.address + 0x588)?;
|
||||
|
||||
let classes: Vec<SchemaClassInfo> = classes
|
||||
.elements(self.process)?
|
||||
.iter()
|
||||
.filter_map(|&address| {
|
||||
let declared_class = SchemaTypeDeclaredClass::new(self.process, address as usize);
|
||||
|
||||
declared_class
|
||||
.name()
|
||||
.ok()
|
||||
.map(|name| SchemaClassInfo::new(self.process, address as usize, &name))
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(classes)
|
||||
}
|
||||
|
||||
pub fn module_name(&self) -> Result<String> {
|
||||
self.process.read_string(self.address + 0x8, 256)
|
||||
}
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
#include "sdk/sdk.hpp"
|
||||
|
||||
namespace sdk {
|
||||
std::string SchemaTypeDeclaredClass::binary_name() const noexcept {
|
||||
return process::read_string(process::read_memory<std::uint64_t>(reinterpret_cast<std::uint64_t>(this) + 0x8), 64);
|
||||
}
|
||||
|
||||
std::string SchemaTypeDeclaredClass::module_name() const noexcept {
|
||||
return process::read_string(process::read_memory<std::uint64_t>(reinterpret_cast<std::uint64_t>(this) + 0x10), 256);
|
||||
}
|
||||
}
|
19
src/sdk/schema_type_declared_class.rs
Normal file
19
src/sdk/schema_type_declared_class.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use crate::error::Result;
|
||||
use crate::remote::Process;
|
||||
|
||||
pub struct SchemaTypeDeclaredClass<'a> {
|
||||
process: &'a Process,
|
||||
address: usize,
|
||||
}
|
||||
|
||||
impl<'a> SchemaTypeDeclaredClass<'a> {
|
||||
pub fn new(process: &'a Process, address: usize) -> Self {
|
||||
Self { process, address }
|
||||
}
|
||||
|
||||
pub fn name(&self) -> Result<String> {
|
||||
let name_ptr = self.process.read_memory::<usize>(self.address + 0x8)?;
|
||||
|
||||
self.process.read_string(name_ptr, 64)
|
||||
}
|
||||
}
|
144
src/sdk/utl_ts_hash.rs
Normal file
144
src/sdk/utl_ts_hash.rs
Normal file
@@ -0,0 +1,144 @@
|
||||
use crate::error::Result;
|
||||
use crate::remote::Process;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct HashBucketDataInternal<T, K> {
|
||||
data: T,
|
||||
pad_0: [u8; 8],
|
||||
ui_key: K,
|
||||
}
|
||||
|
||||
impl<T, K> HashBucketDataInternal<T, K> {
|
||||
pub fn next(&self, process: &Process) -> Result<*const HashBucketDataInternal<T, K>> {
|
||||
process
|
||||
.read_memory::<*const HashBucketDataInternal<T, K>>((self as *const _ as usize) + 0x8)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct HashFixedDataInternal<T, K> {
|
||||
ui_key: K,
|
||||
pad_0: [u8; 8],
|
||||
data: T,
|
||||
}
|
||||
|
||||
impl<T, K> HashFixedDataInternal<T, K> {
|
||||
pub fn next(&self, process: &Process) -> Result<*const HashFixedDataInternal<T, K>> {
|
||||
process.read_memory::<*const HashFixedDataInternal<T, K>>((self as *const _ as usize) + 0x8)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct HashAllocatedData<T, K> {
|
||||
list: [HashFixedDataInternal<T, K>; 128],
|
||||
}
|
||||
|
||||
impl<T, K> HashAllocatedData<T, K> {
|
||||
pub fn list(&self, process: &Process) -> Result<[HashFixedDataInternal<T, K>; 128]> {
|
||||
process
|
||||
.read_memory::<[HashFixedDataInternal<T, K>; 128]>((self as *const _ as usize) + 0x18)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct HashUnallocatedData<T, K> {
|
||||
block_list: [HashBucketDataInternal<T, K>; 256],
|
||||
}
|
||||
|
||||
impl<T, K> HashUnallocatedData<T, K> {
|
||||
pub fn next(&self, process: &Process) -> Result<*const HashUnallocatedData<T, K>> {
|
||||
process.read_memory::<*const HashUnallocatedData<T, K>>(self as *const _ as usize)
|
||||
}
|
||||
|
||||
pub fn ui_key(&self, process: &Process) -> Result<K> {
|
||||
process.read_memory::<K>((self as *const _ as usize) + 0x10)
|
||||
}
|
||||
|
||||
pub fn block_list(&self, process: &Process) -> Result<[HashBucketDataInternal<T, K>; 256]> {
|
||||
process
|
||||
.read_memory::<[HashBucketDataInternal<T, K>; 256]>((self as *const _ as usize) + 0x20)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct HashBucket<T, K> {
|
||||
pad_0: [u8; 16],
|
||||
allocated_data: *const HashAllocatedData<T, K>,
|
||||
unallocated_data: *const HashUnallocatedData<T, K>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct UtlMemoryPool {
|
||||
block_size: i32,
|
||||
blocks_per_blob: i32,
|
||||
grow_mode: i32,
|
||||
blocks_allocated: i32,
|
||||
block_allocated_size: i32,
|
||||
peak_alloc: i32,
|
||||
}
|
||||
|
||||
impl UtlMemoryPool {
|
||||
#[inline]
|
||||
pub fn block_size(&self) -> i32 {
|
||||
self.blocks_per_blob
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn count(&self) -> i32 {
|
||||
self.block_allocated_size
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct UtlTsHash<T, K = u64> {
|
||||
entry_memory: UtlMemoryPool,
|
||||
buckets: HashBucket<T, K>,
|
||||
}
|
||||
|
||||
impl<T, K> UtlTsHash<T, K>
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
#[inline]
|
||||
pub fn block_size(&self) -> i32 {
|
||||
self.entry_memory.block_size()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn count(&self) -> i32 {
|
||||
self.entry_memory.count()
|
||||
}
|
||||
|
||||
pub fn elements(&self, process: &Process) -> Result<Vec<T>> {
|
||||
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() {
|
||||
unsafe {
|
||||
let block_list = (*address).block_list(process)?;
|
||||
|
||||
for i in 0..min_size {
|
||||
list.push(block_list[i].data);
|
||||
|
||||
if list.len() >= self.count() as usize {
|
||||
return Ok(list);
|
||||
}
|
||||
}
|
||||
|
||||
address = (*address).next(process)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(list)
|
||||
}
|
||||
}
|
@@ -1,48 +0,0 @@
|
||||
#include "utility/address.hpp"
|
||||
#include "process.hpp"
|
||||
|
||||
namespace utility {
|
||||
Address Address::add(const std::ptrdiff_t offset) const noexcept {
|
||||
return Address(address_ + offset);
|
||||
}
|
||||
|
||||
std::uintptr_t Address::address() const noexcept {
|
||||
return address_;
|
||||
}
|
||||
|
||||
Address Address::get(const std::size_t times) const noexcept {
|
||||
std::uintptr_t base = address_;
|
||||
|
||||
for (std::size_t i = 0; i < times; ++i)
|
||||
base = process::read_memory<std::uintptr_t>(base);
|
||||
|
||||
return Address(base);
|
||||
}
|
||||
|
||||
bool Address::is_valid() const noexcept {
|
||||
return address_ > static_cast<std::uintptr_t>(0x1000)
|
||||
&& address_ < static_cast<std::uintptr_t>(sizeof(void*) == 4 ? 0x7FFEFFFF : 0x7FFFFFFEFFFF);
|
||||
}
|
||||
|
||||
Address Address::jmp(const std::ptrdiff_t offset) const noexcept {
|
||||
std::uintptr_t base = address_ + offset;
|
||||
|
||||
const auto displacement = process::read_memory<std::int32_t>(address_);
|
||||
|
||||
base += displacement;
|
||||
base += sizeof(std::uint32_t);
|
||||
|
||||
return Address(base);
|
||||
}
|
||||
|
||||
Address Address::rip(const std::ptrdiff_t offset, const std::size_t length) const noexcept {
|
||||
std::uintptr_t base = address_;
|
||||
|
||||
const auto displacement = process::read_memory<std::int32_t>(base + offset);
|
||||
|
||||
base += displacement;
|
||||
base += length;
|
||||
|
||||
return Address(base);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user