Rewrote project in Rust

This commit is contained in:
a2x
2023-09-26 00:46:10 +10:00
parent a8d3318d94
commit 369ebcf238
136 changed files with 47374 additions and 47187 deletions

View 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(())
}
}

View 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(())
}
}

View 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<()>;
}

View 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
View 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),
}
}
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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>;

View File

@@ -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
View 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(())
}

View File

@@ -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
View 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
View 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
View 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
View 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;

View File

@@ -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);
}
}

View 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)
}
}

View File

@@ -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);
}
}
}

View 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)
}
}

View File

@@ -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
View 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)
}
}

View File

@@ -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);
}
}

View 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)
}
}

View File

@@ -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);
}
}

View 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
View 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)
}
}

View File

@@ -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);
}
}