mirror of
https://github.com/a2x/cs2-dumper.git
synced 2025-10-07 16:30:01 +08:00
📦 Game Update 13963 (2)
This commit is contained in:
@@ -2,6 +2,7 @@ use std::io::{Result, Write};
|
||||
|
||||
use super::FileBuilder;
|
||||
|
||||
/// Represents a C++ header file builder.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct CppFileBuilder;
|
||||
|
||||
@@ -17,7 +18,12 @@ impl FileBuilder for CppFileBuilder {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_namespace(&mut self, output: &mut dyn Write, name: &str, comment: Option<&str>) -> Result<()> {
|
||||
fn write_namespace(
|
||||
&mut self,
|
||||
output: &mut dyn Write,
|
||||
name: &str,
|
||||
comment: Option<&str>,
|
||||
) -> Result<()> {
|
||||
if let Some(comment) = comment {
|
||||
write!(output, "namespace {} {{ // {}\n", name, comment)?;
|
||||
} else {
|
||||
|
@@ -2,6 +2,7 @@ use std::io::{Result, Write};
|
||||
|
||||
use super::FileBuilder;
|
||||
|
||||
/// Represents a C# file builder.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct CSharpFileBuilder;
|
||||
|
||||
@@ -14,7 +15,12 @@ impl FileBuilder for CSharpFileBuilder {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_namespace(&mut self, output: &mut dyn Write, name: &str, comment: Option<&str>) -> Result<()> {
|
||||
fn write_namespace(
|
||||
&mut self,
|
||||
output: &mut dyn Write,
|
||||
name: &str,
|
||||
comment: Option<&str>,
|
||||
) -> Result<()> {
|
||||
if let Some(comment) = comment {
|
||||
write!(output, "public static class {} {{ // {}\n", name, comment)?;
|
||||
} else {
|
||||
|
@@ -1,12 +1,22 @@
|
||||
use std::io::{Result, Write};
|
||||
|
||||
/// Represents a file builder.
|
||||
pub trait FileBuilder {
|
||||
/// Returns the file extension.
|
||||
fn extension(&mut self) -> &str;
|
||||
|
||||
/// Writes the top level of the file.
|
||||
fn write_top_level(&mut self, output: &mut dyn Write) -> Result<()>;
|
||||
|
||||
fn write_namespace(&mut self, output: &mut dyn Write, name: &str, comment: Option<&str>) -> Result<()>;
|
||||
/// Writes a namespace.
|
||||
fn write_namespace(
|
||||
&mut self,
|
||||
output: &mut dyn Write,
|
||||
name: &str,
|
||||
comment: Option<&str>,
|
||||
) -> Result<()>;
|
||||
|
||||
/// Writes a variable.
|
||||
fn write_variable(
|
||||
&mut self,
|
||||
output: &mut dyn Write,
|
||||
@@ -15,5 +25,6 @@ pub trait FileBuilder {
|
||||
comment: Option<&str>,
|
||||
) -> Result<()>;
|
||||
|
||||
/// Writes a closure.
|
||||
fn write_closure(&mut self, output: &mut dyn Write, eof: bool) -> Result<()>;
|
||||
}
|
||||
|
@@ -1,25 +1,30 @@
|
||||
use std::{io::{Result, Write}, collections::BTreeMap};
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
io::{Result, Write},
|
||||
};
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
use super::FileBuilder;
|
||||
|
||||
|
||||
/// Represents an offset value in JSON format.
|
||||
#[derive(Debug, PartialEq, Default, Serialize)]
|
||||
struct JsonOffsetValue {
|
||||
value: usize,
|
||||
comment: Option<String>,
|
||||
}
|
||||
|
||||
/// Represents a module in JSON format.
|
||||
#[derive(Debug, PartialEq, Default, Serialize)]
|
||||
struct JsonMod {
|
||||
struct JsonModule {
|
||||
data: BTreeMap<String, JsonOffsetValue>,
|
||||
comment: Option<String>,
|
||||
}
|
||||
|
||||
/// Represents a JSON file builder.
|
||||
#[derive(Debug, PartialEq, Default)]
|
||||
pub struct JsonFileBuilder {
|
||||
data: BTreeMap<String, JsonMod>,
|
||||
data: BTreeMap<String, JsonModule>,
|
||||
current_namespace: String,
|
||||
}
|
||||
|
||||
@@ -32,7 +37,12 @@ impl FileBuilder for JsonFileBuilder {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_namespace(&mut self, _output: &mut dyn Write, name: &str, comment: Option<&str>) -> Result<()> {
|
||||
fn write_namespace(
|
||||
&mut self,
|
||||
_output: &mut dyn Write,
|
||||
name: &str,
|
||||
comment: Option<&str>,
|
||||
) -> Result<()> {
|
||||
self.current_namespace = name.to_string();
|
||||
self.data.entry(name.to_string()).or_default().comment = comment.map(str::to_string);
|
||||
|
||||
@@ -46,11 +56,17 @@ impl FileBuilder for JsonFileBuilder {
|
||||
value: usize,
|
||||
comment: Option<&str>,
|
||||
) -> Result<()> {
|
||||
self.data.entry(self.current_namespace.clone()).or_default().data
|
||||
.insert(name.to_string(), JsonOffsetValue {
|
||||
value: value,
|
||||
comment: comment.map(str::to_string)
|
||||
});
|
||||
self.data
|
||||
.entry(self.current_namespace.clone())
|
||||
.or_default()
|
||||
.data
|
||||
.insert(
|
||||
name.to_string(),
|
||||
JsonOffsetValue {
|
||||
value: value,
|
||||
comment: comment.map(str::to_string),
|
||||
},
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@@ -14,6 +14,7 @@ pub mod json_file_builder;
|
||||
pub mod python_file_builder;
|
||||
pub mod rust_file_builder;
|
||||
|
||||
/// Represents a file builder enum.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum FileBuilderEnum {
|
||||
CppFileBuilder(CppFileBuilder),
|
||||
@@ -32,7 +33,12 @@ impl FileBuilder for FileBuilderEnum {
|
||||
self.as_mut().write_top_level(output)
|
||||
}
|
||||
|
||||
fn write_namespace(&mut self, output: &mut dyn Write, name: &str, comment: Option<&str>) -> Result<()> {
|
||||
fn write_namespace(
|
||||
&mut self,
|
||||
output: &mut dyn Write,
|
||||
name: &str,
|
||||
comment: Option<&str>,
|
||||
) -> Result<()> {
|
||||
self.as_mut().write_namespace(output, name, comment)
|
||||
}
|
||||
|
||||
|
@@ -2,6 +2,7 @@ use std::io::{Result, Write};
|
||||
|
||||
use super::FileBuilder;
|
||||
|
||||
/// Represents a Python file builder.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct PythonFileBuilder;
|
||||
|
||||
@@ -14,7 +15,12 @@ impl FileBuilder for PythonFileBuilder {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_namespace(&mut self, output: &mut dyn Write, name: &str, comment: Option<&str>) -> Result<()> {
|
||||
fn write_namespace(
|
||||
&mut self,
|
||||
output: &mut dyn Write,
|
||||
name: &str,
|
||||
comment: Option<&str>,
|
||||
) -> Result<()> {
|
||||
if let Some(comment) = comment {
|
||||
write!(output, "class {}: # {}\n", name, comment)?;
|
||||
} else {
|
||||
|
@@ -2,6 +2,7 @@ use std::io::{Result, Write};
|
||||
|
||||
use super::FileBuilder;
|
||||
|
||||
/// Represents a Rust file builder.
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct RustFileBuilder;
|
||||
|
||||
@@ -19,7 +20,12 @@ impl FileBuilder for RustFileBuilder {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_namespace(&mut self, output: &mut dyn Write, name: &str, comment: Option<&str>) -> Result<()> {
|
||||
fn write_namespace(
|
||||
&mut self,
|
||||
output: &mut dyn Write,
|
||||
name: &str,
|
||||
comment: Option<&str>,
|
||||
) -> Result<()> {
|
||||
if let Some(comment) = comment {
|
||||
write!(output, "pub mod {} {{ // {}\n", name, comment)?;
|
||||
} else {
|
||||
|
@@ -1,8 +1,9 @@
|
||||
use anyhow::Result;
|
||||
|
||||
use convert_case::{Case, Casing};
|
||||
|
||||
use crate::builder::FileBuilderEnum;
|
||||
use crate::dumpers::Entry;
|
||||
use crate::error::Result;
|
||||
use crate::remote::Process;
|
||||
use crate::sdk::InterfaceReg;
|
||||
|
||||
@@ -21,14 +22,14 @@ pub fn dump_interfaces(builders: &mut Vec<FileBuilderEnum>, process: &Process) -
|
||||
if let Some(create_interface_export) = module.export("CreateInterface") {
|
||||
log::info!("Dumping interfaces in {}...", module_name);
|
||||
|
||||
let create_interface_address =
|
||||
process.resolve_rip(create_interface_export.va, None, None)?;
|
||||
let create_interface_addr =
|
||||
process.resolve_rip(create_interface_export.va.into(), None, None)?;
|
||||
|
||||
if let Ok(mut interface_reg) =
|
||||
process.read_memory::<*mut InterfaceReg>(create_interface_address)
|
||||
process.read_memory::<*mut InterfaceReg>(create_interface_addr)
|
||||
{
|
||||
while !interface_reg.is_null() {
|
||||
let ptr = unsafe { (*interface_reg).ptr(process) }?;
|
||||
let ptr = unsafe { (*interface_reg).pointer(process) }?;
|
||||
let name = unsafe { (*interface_reg).name(process) }?;
|
||||
|
||||
log::debug!(
|
||||
@@ -36,23 +37,25 @@ pub fn dump_interfaces(builders: &mut Vec<FileBuilderEnum>, process: &Process) -
|
||||
name,
|
||||
ptr,
|
||||
module_name,
|
||||
ptr - module.base()
|
||||
ptr - module.base_address()
|
||||
);
|
||||
|
||||
entries
|
||||
let container = entries
|
||||
.entry(
|
||||
module_name
|
||||
.replace(".", "_")
|
||||
.to_case(Case::Snake)
|
||||
.to_case(Case::Pascal),
|
||||
)
|
||||
.or_default()
|
||||
.data
|
||||
.push(Entry {
|
||||
name: name.clone(),
|
||||
value: ptr - module.base(),
|
||||
comment: None,
|
||||
});
|
||||
.or_default();
|
||||
|
||||
container.comment = Some(module_name.clone());
|
||||
|
||||
container.data.push(Entry {
|
||||
name: name.clone(),
|
||||
value: (ptr - module.base_address()).into(),
|
||||
comment: None,
|
||||
});
|
||||
|
||||
interface_reg = unsafe { (*interface_reg).next(process) }?;
|
||||
}
|
||||
|
@@ -2,10 +2,11 @@ use std::collections::BTreeMap;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use chrono::Utc;
|
||||
|
||||
use crate::builder::{FileBuilder, FileBuilderEnum};
|
||||
use crate::error::Result;
|
||||
|
||||
pub use interfaces::dump_interfaces;
|
||||
pub use offsets::dump_offsets;
|
||||
@@ -15,20 +16,25 @@ pub mod interfaces;
|
||||
pub mod offsets;
|
||||
pub mod schemas;
|
||||
|
||||
/// A single entry.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Entry {
|
||||
pub name: String,
|
||||
pub value: usize,
|
||||
pub comment: Option<String>,
|
||||
}
|
||||
|
||||
/// A container for entries.
|
||||
#[derive(Default)]
|
||||
pub struct EntriesContainer {
|
||||
pub data: Vec<Entry>,
|
||||
pub comment: Option<String>
|
||||
pub comment: Option<String>,
|
||||
}
|
||||
|
||||
/// A map of entries.
|
||||
pub type Entries = BTreeMap<String, EntriesContainer>;
|
||||
|
||||
/// Generate a file with the given builder.
|
||||
pub fn generate_file(
|
||||
builder: &mut FileBuilderEnum,
|
||||
entries: &Entries,
|
||||
@@ -66,6 +72,7 @@ pub fn generate_file(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generate files with the given builders.
|
||||
pub fn generate_files(
|
||||
builders: &mut [FileBuilderEnum],
|
||||
entries: &Entries,
|
||||
@@ -76,15 +83,16 @@ pub fn generate_files(
|
||||
.try_for_each(|builder| generate_file(builder, entries, file_name))
|
||||
}
|
||||
|
||||
fn write_banner_to_file(file: &mut File, extension: &str) -> Result<()> {
|
||||
let chrono_now = Utc::now();
|
||||
/// Writes the banner to the given file.
|
||||
fn write_banner_to_file(file: &mut File, file_ext: &str) -> Result<()> {
|
||||
const REPO_URL: &str = "https://github.com/a2x/cs2-dumper";
|
||||
|
||||
const URL: &str = "https://github.com/a2x/cs2-dumper";
|
||||
let now_utc = Utc::now();
|
||||
|
||||
let banner = match extension {
|
||||
let banner = match file_ext {
|
||||
"json" => None,
|
||||
"py" => Some(format!("'''\n{}\n{}\n'''\n\n", URL, chrono_now)),
|
||||
_ => Some(format!("/*\n * {}\n * {}\n */\n\n", URL, chrono_now)),
|
||||
"py" => Some(format!("'''\n{}\n{}\n'''\n\n", REPO_URL, now_utc)),
|
||||
_ => Some(format!("/*\n * {}\n * {}\n */\n\n", REPO_URL, now_utc)),
|
||||
};
|
||||
|
||||
if let Some(banner) = banner {
|
||||
|
@@ -1,25 +1,28 @@
|
||||
use std::fs::File;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use convert_case::{Case, Casing};
|
||||
|
||||
use crate::builder::FileBuilderEnum;
|
||||
use crate::config::{Config, Operation::*};
|
||||
use crate::dumpers::Entry;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::mem::Address;
|
||||
use crate::remote::Process;
|
||||
|
||||
use super::{generate_files, Entries};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::ffi::c_char;
|
||||
use std::mem::offset_of;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn build_number() -> Result<()> {
|
||||
let process = Process::new("cs2.exe")?;
|
||||
|
||||
let engine_base = process.get_module_by_name("engine2.dll")?.base();
|
||||
let engine_base = process.get_module_by_name("engine2.dll")?.base_address();
|
||||
|
||||
let build_number = process.read_memory::<u32>(engine_base + 0x487514)?;
|
||||
|
||||
@@ -30,14 +33,54 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn global_vars() -> Result<()> {
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
struct GlobalVarsBase {
|
||||
real_time: f32, // 0x0000
|
||||
frame_count: i32, // 0x0004
|
||||
pad_0: [u8; 0x8], // 0x0008
|
||||
max_clients: i32, // 0x0010
|
||||
interval_per_tick: f32, // 0x0014
|
||||
pad_1: [u8; 0x14], // 0x0018
|
||||
current_time: f32, // 0x002C
|
||||
current_time2: f32, // 0x0030
|
||||
pad_2: [u8; 0xC], // 0x0034
|
||||
tick_count: i32, // 0x0040
|
||||
interval_per_tick2: f32, // 0x0044
|
||||
pad_3: [u8; 0x138], // 0x0048
|
||||
current_map: *const c_char, // 0x0180
|
||||
current_map_name: *const c_char, // 0x0188
|
||||
}
|
||||
|
||||
impl GlobalVarsBase {
|
||||
fn current_map(&self, process: &Process) -> Result<String> {
|
||||
let name_ptr = process.read_memory::<usize>(
|
||||
(self as *const _ as usize + offset_of!(Self, current_map)).into(),
|
||||
)?;
|
||||
|
||||
process.read_string(name_ptr.into())
|
||||
}
|
||||
|
||||
fn current_map_name(&self, process: &Process) -> Result<String> {
|
||||
let name_ptr = process.read_memory::<usize>(
|
||||
(self as *const _ as usize + offset_of!(Self, current_map_name)).into(),
|
||||
)?;
|
||||
|
||||
process.read_string(name_ptr.into())
|
||||
}
|
||||
}
|
||||
|
||||
let process = Process::new("cs2.exe")?;
|
||||
|
||||
let client_base = process.get_module_by_name("client.dll")?.base();
|
||||
let client_base = process.get_module_by_name("client.dll")?.base_address();
|
||||
|
||||
let global_vars = process.read_memory::<usize>(client_base + 0x1696F40)?;
|
||||
let global_vars = process.read_memory::<*const GlobalVarsBase>(client_base + 0x1696F40)?;
|
||||
|
||||
let current_map_name =
|
||||
process.read_string(process.read_memory::<usize>(global_vars + 0x188)?)?;
|
||||
let current_map_name = unsafe {
|
||||
(*global_vars)
|
||||
.current_map_name(&process)
|
||||
.unwrap_or_default()
|
||||
};
|
||||
|
||||
println!("Current map name: {}", current_map_name);
|
||||
|
||||
@@ -48,11 +91,11 @@ mod tests {
|
||||
fn local_player() -> Result<()> {
|
||||
let process = Process::new("cs2.exe")?;
|
||||
|
||||
let client_base = process.get_module_by_name("client.dll")?.base();
|
||||
let client_base = process.get_module_by_name("client.dll")?.base_address();
|
||||
|
||||
let local_player_controller = process.read_memory::<usize>(client_base + 0x17DE508)?;
|
||||
let local_player_controller = process.read_memory::<usize>(client_base + 0x17E27C8)?;
|
||||
|
||||
let player_name = process.read_string(local_player_controller + 0x610)?;
|
||||
let player_name = process.read_string((local_player_controller + 0x610).into())?;
|
||||
|
||||
println!("Local player name: {}", player_name);
|
||||
|
||||
@@ -63,10 +106,10 @@ mod tests {
|
||||
fn window_size() -> Result<()> {
|
||||
let process = Process::new("cs2.exe")?;
|
||||
|
||||
let engine_base = process.get_module_by_name("engine2.dll")?.base();
|
||||
let engine_base = process.get_module_by_name("engine2.dll")?.base_address();
|
||||
|
||||
let window_width = process.read_memory::<u32>(engine_base + 0x538670)?;
|
||||
let window_height = process.read_memory::<u32>(engine_base + 0x538674)?;
|
||||
let window_width = process.read_memory::<u32>(engine_base + 0x5386D0)?;
|
||||
let window_height = process.read_memory::<u32>(engine_base + 0x5386D4)?;
|
||||
|
||||
println!("Window size: {}x{}", window_width, window_height);
|
||||
|
||||
@@ -77,7 +120,7 @@ mod tests {
|
||||
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 config: Config = serde_json::from_reader(file)?;
|
||||
|
||||
let mut entries = Entries::new();
|
||||
|
||||
@@ -85,10 +128,11 @@ pub fn dump_offsets(builders: &mut Vec<FileBuilderEnum>, process: &Process) -> R
|
||||
|
||||
for signature in config.signatures {
|
||||
log::info!("Searching for {}...", signature.name);
|
||||
|
||||
let module = process.get_module_by_name(&signature.module)?;
|
||||
|
||||
let mut address = match process.find_pattern(&signature.module, &signature.pattern) {
|
||||
Ok(a) => Address::from(a),
|
||||
let mut addr = match process.find_pattern(&signature.module, &signature.pattern) {
|
||||
Ok(a) => a,
|
||||
Err(_) => {
|
||||
log::error!("Failed to find pattern for {}.", signature.name);
|
||||
|
||||
@@ -96,59 +140,53 @@ pub fn dump_offsets(builders: &mut Vec<FileBuilderEnum>, process: &Process) -> R
|
||||
}
|
||||
};
|
||||
|
||||
for operation in signature.operations {
|
||||
match operation {
|
||||
Add { value } => address += value,
|
||||
for op in signature.operations {
|
||||
match op {
|
||||
Add { value } => addr += value,
|
||||
Dereference { times, size } => {
|
||||
let times = times.unwrap_or(1);
|
||||
let size = size.unwrap_or(8);
|
||||
|
||||
for _ in 0..times {
|
||||
process.read_memory_raw(
|
||||
address.0,
|
||||
&mut address.0 as *mut _ as *mut _,
|
||||
size,
|
||||
)?;
|
||||
process.read_memory_raw(addr, &mut addr.0 as *mut _ as *mut _, size)?;
|
||||
}
|
||||
}
|
||||
Jmp { offset, length } => {
|
||||
address = process.resolve_jmp(address.0, offset, length)?.into()
|
||||
}
|
||||
Jmp { offset, length } => addr = process.resolve_jmp(addr, offset, length)?.into(),
|
||||
RipRelative { offset, length } => {
|
||||
address = process.resolve_rip(address.0, offset, length)?.into()
|
||||
addr = process.resolve_rip(addr, offset, length)?.into()
|
||||
}
|
||||
Slice { start, end } => {
|
||||
let mut result: usize = 0;
|
||||
|
||||
process.read_memory_raw(
|
||||
address.add(start).0,
|
||||
addr.add(start),
|
||||
&mut result as *mut _ as *mut _,
|
||||
end - start,
|
||||
)?;
|
||||
|
||||
address = result.into();
|
||||
addr = result.into();
|
||||
}
|
||||
Subtract { value } => address -= value,
|
||||
Subtract { value } => addr -= value,
|
||||
}
|
||||
}
|
||||
|
||||
let (name, value) = if address.0 < module.base() {
|
||||
log::debug!(" └─ {} @ {:#X}", signature.name, address.0);
|
||||
let (name, value) = if addr < module.base_address() {
|
||||
log::debug!(" └─ {} @ {:#X}", signature.name, addr.0);
|
||||
|
||||
(signature.name, address.0)
|
||||
(signature.name, addr.0)
|
||||
} else {
|
||||
log::debug!(
|
||||
" └─ {} @ {:#X} ({} + {:#X})",
|
||||
signature.name,
|
||||
address,
|
||||
addr,
|
||||
signature.module,
|
||||
address.sub(module.base())
|
||||
addr.sub(module.base_address().0)
|
||||
);
|
||||
|
||||
(signature.name, address.sub(module.base()).0)
|
||||
(signature.name, addr.sub(module.base_address().0).0)
|
||||
};
|
||||
|
||||
entries
|
||||
let container = entries
|
||||
.entry(
|
||||
signature
|
||||
.module
|
||||
@@ -156,13 +194,15 @@ pub fn dump_offsets(builders: &mut Vec<FileBuilderEnum>, process: &Process) -> R
|
||||
.to_case(Case::Snake)
|
||||
.to_case(Case::Pascal),
|
||||
)
|
||||
.or_default()
|
||||
.data
|
||||
.push(Entry {
|
||||
name,
|
||||
value,
|
||||
comment: None,
|
||||
});
|
||||
.or_default();
|
||||
|
||||
container.comment = Some(signature.module);
|
||||
|
||||
container.data.push(Entry {
|
||||
name,
|
||||
value,
|
||||
comment: None,
|
||||
});
|
||||
}
|
||||
|
||||
generate_files(builders, &entries, "offsets")?;
|
||||
|
@@ -1,6 +1,7 @@
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::builder::FileBuilderEnum;
|
||||
use crate::dumpers::Entry;
|
||||
use crate::error::Result;
|
||||
use crate::remote::Process;
|
||||
use crate::sdk::SchemaSystem;
|
||||
|
||||
@@ -20,6 +21,7 @@ pub fn dump_schemas(builders: &mut Vec<FileBuilderEnum>, process: &Process) -> R
|
||||
log::debug!(" {}", class.name());
|
||||
|
||||
let container = entries.entry(class.name().replace("::", "_")).or_default();
|
||||
|
||||
container.comment = class.parent()?.map(|p| p.name().to_string());
|
||||
|
||||
for field in class.fields()? {
|
||||
|
40
src/error.rs
40
src/error.rs
@@ -1,40 +0,0 @@
|
||||
use std::io;
|
||||
use std::string::FromUtf8Error;
|
||||
|
||||
use serde_json::Error as SerdeError;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use windows::core::Error as WindowsError;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("Buffer size mismatch. Expected {0}, got {1}")]
|
||||
BufferSizeMismatch(usize, usize),
|
||||
|
||||
#[error("Invalid magic: {0:#X}")]
|
||||
InvalidMagic(u32),
|
||||
|
||||
#[error("IO error: {0}")]
|
||||
IOError(#[from] io::Error),
|
||||
|
||||
#[error("Module not found: {0}")]
|
||||
ModuleNotFound(String),
|
||||
|
||||
#[error("Pattern not found: {0}")]
|
||||
PatternNotFound(String),
|
||||
|
||||
#[error("Process not found: {0}")]
|
||||
ProcessNotFound(String),
|
||||
|
||||
#[error("Serde error: {0}")]
|
||||
SerdeError(#[from] SerdeError),
|
||||
|
||||
#[error("UTF-8 error: {0}")]
|
||||
Utf8Error(#[from] FromUtf8Error),
|
||||
|
||||
#[error("Windows error: {0}")]
|
||||
WindowsError(#[from] WindowsError),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
@@ -4,19 +4,19 @@
|
||||
use std::fs;
|
||||
use std::time::Instant;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
|
||||
use builder::*;
|
||||
use dumpers::*;
|
||||
use error::Result;
|
||||
use remote::Process;
|
||||
|
||||
mod builder;
|
||||
mod config;
|
||||
mod dumpers;
|
||||
mod error;
|
||||
mod mem;
|
||||
mod remote;
|
||||
mod sdk;
|
||||
|
@@ -1,23 +1,33 @@
|
||||
use std::fmt::{LowerHex, UpperHex};
|
||||
use std::ops::{Add, AddAssign, Sub, SubAssign};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
/// Represents a memory address.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd)]
|
||||
#[repr(transparent)]
|
||||
pub struct Address(pub usize);
|
||||
|
||||
impl Address {
|
||||
/// Add the given value to the address.
|
||||
pub fn add(&self, value: usize) -> Self {
|
||||
Self(self.0 + value)
|
||||
}
|
||||
|
||||
/// Returns `true` if the address is zero.
|
||||
pub fn is_zero(&self) -> bool {
|
||||
self.0 == 0
|
||||
}
|
||||
|
||||
/// Subtract the given value from the address.
|
||||
pub fn sub(&self, value: usize) -> Self {
|
||||
Self(self.0 - value)
|
||||
}
|
||||
|
||||
/// Get the address as a pointer.
|
||||
pub fn as_ptr<T>(&self) -> *const T {
|
||||
self.0 as *const T
|
||||
}
|
||||
|
||||
/// Get the address as a mutable pointer.
|
||||
pub fn as_mut_ptr<T>(&self) -> *mut T {
|
||||
self.0 as *mut T
|
||||
}
|
||||
|
@@ -2,161 +2,201 @@ use std::ffi::CStr;
|
||||
use std::mem;
|
||||
use std::slice;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
use windows::Win32::System::Diagnostics::Debug::*;
|
||||
use windows::Win32::System::SystemServices::*;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::mem::Address;
|
||||
|
||||
use super::Process;
|
||||
|
||||
/// Represents an export in a module.
|
||||
#[derive(Debug)]
|
||||
pub struct Export {
|
||||
/// Name of the export.
|
||||
pub name: String,
|
||||
|
||||
/// Address of the export.
|
||||
pub va: usize,
|
||||
}
|
||||
|
||||
/// Represents a section in a module.
|
||||
#[derive(Debug)]
|
||||
pub struct Section {
|
||||
/// Name of the section.
|
||||
pub name: String,
|
||||
|
||||
/// Base address of the section.
|
||||
pub start_va: usize,
|
||||
|
||||
/// End address of the section.
|
||||
pub end_va: usize,
|
||||
|
||||
/// Size of the section.
|
||||
pub size: usize,
|
||||
}
|
||||
|
||||
/// Represents a module in a process.
|
||||
pub struct Module<'a> {
|
||||
base: usize,
|
||||
nt_headers: &'a IMAGE_NT_HEADERS64,
|
||||
/// Base address of the module.
|
||||
base_addr: Address,
|
||||
|
||||
/// DOS header.
|
||||
dos_hdr: &'a IMAGE_DOS_HEADER,
|
||||
|
||||
/// NT header.
|
||||
nt_hdr: &'a IMAGE_NT_HEADERS64,
|
||||
|
||||
/// Size of the module.
|
||||
size: u32,
|
||||
|
||||
/// List of exports.
|
||||
exports: Vec<Export>,
|
||||
|
||||
/// List of sections.
|
||||
sections: Vec<Section>,
|
||||
}
|
||||
|
||||
impl<'a> Module<'a> {
|
||||
pub fn new(process: &'a Process, base: usize) -> Result<Self> {
|
||||
let mut headers: [u8; 0x1000] = [0; 0x1000];
|
||||
pub fn new(process: &'a Process, base_addr: Address) -> Result<Self> {
|
||||
let mut buf: [u8; 0x1000] = [0; 0x1000];
|
||||
|
||||
process.read_memory_raw(base, headers.as_mut_ptr() as *mut _, headers.len())?;
|
||||
process.read_memory_raw(base_addr, buf.as_mut_ptr() as *mut _, buf.len())?;
|
||||
|
||||
if headers.len() < mem::size_of::<IMAGE_DOS_HEADER>() {
|
||||
return Err(Error::BufferSizeMismatch(
|
||||
if buf.len() < mem::size_of::<IMAGE_DOS_HEADER>() {
|
||||
bail!(
|
||||
"Buffer size mismatch. Expected {} bytes, got {} bytes",
|
||||
mem::size_of::<IMAGE_DOS_HEADER>(),
|
||||
headers.len(),
|
||||
));
|
||||
buf.len()
|
||||
);
|
||||
}
|
||||
|
||||
let dos_header = unsafe { &*(headers.as_ptr() as *const IMAGE_DOS_HEADER) };
|
||||
let dos_hdr = unsafe { &*(buf.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));
|
||||
if dos_hdr.e_magic != IMAGE_DOS_SIGNATURE {
|
||||
bail!(
|
||||
"Invalid DOS signature. Expected 0x{:X}, got 0x{:X}",
|
||||
IMAGE_DOS_SIGNATURE,
|
||||
dos_hdr.e_magic
|
||||
);
|
||||
}
|
||||
|
||||
let nt_headers = unsafe {
|
||||
&*(headers.as_ptr().offset(dos_header.e_lfanew as isize) as *const IMAGE_NT_HEADERS64)
|
||||
let nt_hdr = unsafe {
|
||||
&*(buf.as_ptr().offset(dos_hdr.e_lfanew as isize) as *const IMAGE_NT_HEADERS64)
|
||||
};
|
||||
|
||||
if nt_headers.Signature != IMAGE_NT_SIGNATURE {
|
||||
return Err(Error::InvalidMagic(nt_headers.Signature));
|
||||
if nt_hdr.Signature != IMAGE_NT_SIGNATURE {
|
||||
bail!(
|
||||
"Invalid NT signature. Expected 0x{:X}, got 0x{:X}",
|
||||
IMAGE_NT_SIGNATURE,
|
||||
nt_hdr.Signature
|
||||
);
|
||||
}
|
||||
|
||||
let size = nt_headers.OptionalHeader.SizeOfImage;
|
||||
let size = nt_hdr.OptionalHeader.SizeOfImage;
|
||||
|
||||
let exports = unsafe { Self::parse_exports(process, base, size, nt_headers)? };
|
||||
let sections = unsafe { Self::parse_sections(base, nt_headers) };
|
||||
let exports = unsafe { Self::parse_exports(process, base_addr, size, nt_hdr)? };
|
||||
let sections = unsafe { Self::parse_sections(base_addr, nt_hdr) };
|
||||
|
||||
Ok(Self {
|
||||
base,
|
||||
nt_headers,
|
||||
base_addr,
|
||||
dos_hdr,
|
||||
nt_hdr,
|
||||
size,
|
||||
exports,
|
||||
sections,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the base address of the module.
|
||||
#[inline]
|
||||
pub fn base(&self) -> usize {
|
||||
self.base
|
||||
pub fn base_address(&self) -> Address {
|
||||
self.base_addr
|
||||
}
|
||||
|
||||
/// Returns the exports of the module.
|
||||
#[inline]
|
||||
pub fn exports(&self) -> &Vec<Export> {
|
||||
&self.exports
|
||||
}
|
||||
|
||||
/// Returns the sections of the module.
|
||||
#[inline]
|
||||
pub fn sections(&self) -> &Vec<Section> {
|
||||
&self.sections
|
||||
}
|
||||
|
||||
/// Returns the export with the given name.
|
||||
#[inline]
|
||||
pub fn export(&self, name: &str) -> Option<&Export> {
|
||||
self.exports.iter().find(|export| export.name == name)
|
||||
}
|
||||
|
||||
/// Returns the section with the given name.
|
||||
#[inline]
|
||||
pub fn section(&self, name: &str) -> Option<&Section> {
|
||||
self.sections.iter().find(|section| section.name == name)
|
||||
}
|
||||
|
||||
/// Returns the size of the module.
|
||||
#[inline]
|
||||
pub fn size(&self) -> u32 {
|
||||
self.size
|
||||
}
|
||||
|
||||
/// Parses the exports of the module.
|
||||
unsafe fn parse_exports(
|
||||
process: &Process,
|
||||
address: usize,
|
||||
base_addr: Address,
|
||||
size: u32,
|
||||
nt_headers: &IMAGE_NT_HEADERS64,
|
||||
nt_hdr: &IMAGE_NT_HEADERS64,
|
||||
) -> Result<Vec<Export>> {
|
||||
let export_data_directory =
|
||||
nt_headers.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT.0 as usize];
|
||||
let export_data_dir =
|
||||
nt_hdr.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 export_dir_start = base_addr + export_data_dir.VirtualAddress as usize;
|
||||
let export_dir_end = export_dir_start + export_data_dir.Size as usize;
|
||||
|
||||
let mut buffer: Vec<u8> = vec![0; export_data_directory.Size as usize];
|
||||
let mut buf: Vec<u8> = vec![0; export_data_dir.Size as usize];
|
||||
|
||||
process.read_memory_raw(
|
||||
export_directory_start,
|
||||
buffer.as_mut_ptr() as *mut _,
|
||||
buffer.len(),
|
||||
)?;
|
||||
process.read_memory_raw(export_dir_start, buf.as_mut_ptr() as *mut _, buf.len())?;
|
||||
|
||||
if buffer.len() < mem::size_of::<IMAGE_EXPORT_DIRECTORY>() {
|
||||
return Err(Error::BufferSizeMismatch(
|
||||
if buf.len() < mem::size_of::<IMAGE_EXPORT_DIRECTORY>() {
|
||||
bail!(
|
||||
"Buffer size mismatch. Expected {} bytes, got {} bytes",
|
||||
mem::size_of::<IMAGE_EXPORT_DIRECTORY>(),
|
||||
buffer.len(),
|
||||
));
|
||||
buf.len()
|
||||
);
|
||||
}
|
||||
|
||||
let export_directory = &*(buffer.as_ptr() as *const IMAGE_EXPORT_DIRECTORY);
|
||||
let export_dir = &*(buf.as_ptr() as *const IMAGE_EXPORT_DIRECTORY);
|
||||
|
||||
let delta =
|
||||
export_directory as *const _ as usize - export_data_directory.VirtualAddress as usize;
|
||||
let delta = export_dir as *const _ as usize - export_data_dir.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 name_table = (delta + export_dir.AddressOfNames as usize) as *const u32;
|
||||
let ordinal_table = (delta + export_dir.AddressOfNameOrdinals as usize) as *const u16;
|
||||
let function_table = (delta + export_dir.AddressOfFunctions as usize) as *const u32;
|
||||
|
||||
let mut exports: Vec<Export> = Vec::with_capacity(export_directory.NumberOfNames as usize);
|
||||
let mut exports: Vec<Export> = Vec::with_capacity(export_dir.NumberOfNames as usize);
|
||||
|
||||
for i in 0..export_directory.NumberOfNames {
|
||||
let target = ordinal_table as usize + i as usize * mem::size_of::<u16>();
|
||||
for i in 0..export_dir.NumberOfNames {
|
||||
let target_va = ordinal_table as usize + i as usize * mem::size_of::<u16>();
|
||||
|
||||
if target > address + size as usize || target < ordinal_table as usize {
|
||||
if target_va > base_addr.add(size as usize).0 || target_va < ordinal_table as usize {
|
||||
continue;
|
||||
}
|
||||
|
||||
let function_ordinal = *ordinal_table.offset(i as isize);
|
||||
let func_ordinal = *ordinal_table.offset(i as isize);
|
||||
|
||||
if function_ordinal as usize > export_directory.NumberOfFunctions as usize {
|
||||
if func_ordinal as usize > export_dir.NumberOfFunctions as usize {
|
||||
continue;
|
||||
}
|
||||
|
||||
let function_va = address + *function_table.offset(function_ordinal as isize) as usize;
|
||||
let func_va = base_addr.add(*function_table.offset(func_ordinal as isize) as usize);
|
||||
|
||||
// Skip forwarded exports.
|
||||
if function_va >= export_directory_start && function_va <= export_directory_end {
|
||||
if func_va >= export_dir_start && func_va <= export_dir_end {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -170,24 +210,22 @@ impl<'a> Module<'a> {
|
||||
|
||||
exports.push(Export {
|
||||
name,
|
||||
va: function_va,
|
||||
va: func_va.0,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(exports)
|
||||
}
|
||||
|
||||
unsafe fn parse_sections(address: usize, nt_headers: &IMAGE_NT_HEADERS64) -> Vec<Section> {
|
||||
let optional_header_ptr = &nt_headers.OptionalHeader as *const _ as *const u8;
|
||||
/// Parses the sections of the module.
|
||||
unsafe fn parse_sections(base_addr: Address, nt_hdr: &IMAGE_NT_HEADERS64) -> Vec<Section> {
|
||||
let optional_hdr = &nt_hdr.OptionalHeader as *const _ as *const u8;
|
||||
|
||||
let section_header_ptr = optional_header_ptr
|
||||
.offset(nt_headers.FileHeader.SizeOfOptionalHeader as isize)
|
||||
let section_hdr = optional_hdr.offset(nt_hdr.FileHeader.SizeOfOptionalHeader as isize)
|
||||
as *const IMAGE_SECTION_HEADER;
|
||||
|
||||
let sections_raw = slice::from_raw_parts(
|
||||
section_header_ptr,
|
||||
nt_headers.FileHeader.NumberOfSections as usize,
|
||||
);
|
||||
let sections_raw =
|
||||
slice::from_raw_parts(section_hdr, nt_hdr.FileHeader.NumberOfSections as usize);
|
||||
|
||||
sections_raw
|
||||
.iter()
|
||||
@@ -199,15 +237,15 @@ impl<'a> Module<'a> {
|
||||
let start_rva = section.VirtualAddress as usize;
|
||||
let end_rva = start_rva + section.Misc.VirtualSize as usize;
|
||||
|
||||
let start_va = address + start_rva;
|
||||
let end_va = address + end_rva;
|
||||
let start_va = base_addr + start_rva;
|
||||
let end_va = base_addr + end_rva;
|
||||
|
||||
let size = section.SizeOfRawData as usize;
|
||||
|
||||
Section {
|
||||
name,
|
||||
start_va,
|
||||
end_va,
|
||||
start_va: start_va.0,
|
||||
end_va: end_va.0,
|
||||
size,
|
||||
}
|
||||
})
|
||||
|
@@ -2,15 +2,18 @@ use std::ffi::{c_void, CStr};
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
use windows::Win32::Foundation::*;
|
||||
use windows::Win32::System::Diagnostics::Debug::*;
|
||||
use windows::Win32::System::Diagnostics::ToolHelp::*;
|
||||
use windows::Win32::System::Threading::*;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::mem::Address;
|
||||
|
||||
use super::Module;
|
||||
|
||||
/// Represents a Win32 process.
|
||||
#[derive(Debug)]
|
||||
pub struct Process {
|
||||
process_id: u32,
|
||||
@@ -29,24 +32,25 @@ impl Process {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn find_pattern(&self, module_name: &str, pattern: &str) -> Result<usize> {
|
||||
/// Finds an sequence of bytes in memory.
|
||||
pub fn find_pattern(&self, module_name: &str, pattern: &str) -> Result<Address> {
|
||||
let module = self.get_module_by_name(module_name)?;
|
||||
|
||||
let mut module_data: Vec<u8> = vec![0; module.size() as usize];
|
||||
|
||||
self.read_memory_raw(
|
||||
module.base(),
|
||||
module.base_address(),
|
||||
module_data.as_mut_ptr() as *mut _,
|
||||
module_data.len(),
|
||||
)?;
|
||||
|
||||
let pattern_bytes = Self::pattern_to_bytes(pattern);
|
||||
let pat_bytes = Self::pattern_to_bytes(pattern);
|
||||
|
||||
for i in 0..module.size() as usize - pattern.len() {
|
||||
let mut found = true;
|
||||
|
||||
for j in 0..pattern_bytes.len() {
|
||||
if module_data[i + j] != pattern_bytes[j] as u8 && pattern_bytes[j] != -1 {
|
||||
for j in 0..pat_bytes.len() {
|
||||
if module_data[i + j] != pat_bytes[j] as u8 && pat_bytes[j] != -1 {
|
||||
found = false;
|
||||
|
||||
break;
|
||||
@@ -54,13 +58,14 @@ impl Process {
|
||||
}
|
||||
|
||||
if found {
|
||||
return Ok(module.base() + i);
|
||||
return Ok(module.base_address() + i);
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::PatternNotFound(pattern.to_owned()))
|
||||
bail!("Pattern not found: {}", pattern)
|
||||
}
|
||||
|
||||
/// Returns a list of loaded modules.
|
||||
pub fn get_loaded_modules(&self) -> Result<Vec<String>> {
|
||||
let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, self.process_id) }?;
|
||||
|
||||
@@ -86,6 +91,7 @@ impl Process {
|
||||
Ok(modules)
|
||||
}
|
||||
|
||||
/// Returns a module by name.
|
||||
pub fn get_module_by_name(&self, module_name: &str) -> Result<Module> {
|
||||
let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, self.process_id) }?;
|
||||
|
||||
@@ -103,20 +109,21 @@ impl Process {
|
||||
.into_owned();
|
||||
|
||||
if name == module_name {
|
||||
return Module::new(self, entry.modBaseAddr as usize);
|
||||
return Module::new(self, Address::from(entry.modBaseAddr as usize));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::ModuleNotFound(module_name.to_owned()))
|
||||
bail!("Module not found: {}", module_name)
|
||||
}
|
||||
|
||||
pub fn read_memory_raw(&self, address: usize, buffer: *mut c_void, size: usize) -> Result<()> {
|
||||
/// Reads raw data from memory.
|
||||
pub fn read_memory_raw(&self, addr: Address, buf: *mut c_void, size: usize) -> Result<()> {
|
||||
unsafe {
|
||||
ReadProcessMemory(
|
||||
self.process_handle,
|
||||
address as *const _,
|
||||
buffer,
|
||||
addr.as_ptr(),
|
||||
buf,
|
||||
size,
|
||||
Some(ptr::null_mut()),
|
||||
)
|
||||
@@ -124,17 +131,13 @@ impl Process {
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn write_memory_raw(
|
||||
&self,
|
||||
address: usize,
|
||||
buffer: *const c_void,
|
||||
size: usize,
|
||||
) -> Result<()> {
|
||||
/// Writes raw data to memory.
|
||||
pub fn write_memory_raw(&self, addr: Address, buf: *const c_void, size: usize) -> Result<()> {
|
||||
unsafe {
|
||||
WriteProcessMemory(
|
||||
self.process_handle,
|
||||
address as *const _,
|
||||
buffer,
|
||||
addr.as_ptr(),
|
||||
buf,
|
||||
size,
|
||||
Some(ptr::null_mut()),
|
||||
)
|
||||
@@ -142,57 +145,72 @@ impl Process {
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn read_memory<T>(&self, address: usize) -> Result<T> {
|
||||
let mut buffer: T = unsafe { mem::zeroed() };
|
||||
/// Reads a value from memory.
|
||||
pub fn read_memory<T>(&self, addr: Address) -> Result<T> {
|
||||
let mut buf: T = unsafe { mem::zeroed() };
|
||||
|
||||
self.read_memory_raw(
|
||||
address,
|
||||
&mut buffer as *const _ as *mut _,
|
||||
mem::size_of::<T>(),
|
||||
)?;
|
||||
self.read_memory_raw(addr, &mut buf as *const _ as *mut _, mem::size_of::<T>())?;
|
||||
|
||||
Ok(buffer)
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
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>())
|
||||
/// Writes a value to memory.
|
||||
pub fn write_memory<T>(&self, addr: Address, val: T) -> Result<()> {
|
||||
self.write_memory_raw(addr, &val as *const _ as *const _, mem::size_of::<T>())
|
||||
}
|
||||
|
||||
pub fn read_string(&self, address: usize) -> Result<String> {
|
||||
let mut buffer = Vec::new();
|
||||
/// Reads a string.
|
||||
pub fn read_string(&self, addr: Address) -> Result<String> {
|
||||
let mut buf = Vec::new();
|
||||
|
||||
for i in 0.. {
|
||||
match self.read_memory::<u8>(address + i) {
|
||||
Ok(byte) if byte != 0 => buffer.push(byte),
|
||||
match self.read_memory::<u8>(addr + i) {
|
||||
Ok(byte) if byte != 0 => buf.push(byte),
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(String::from_utf8(buffer)?)
|
||||
Ok(String::from_utf8(buf)?)
|
||||
}
|
||||
|
||||
/// Reads a string with a specified length.
|
||||
pub fn read_string_len(&self, addr: Address, len: usize) -> Result<String> {
|
||||
let mut buf: Vec<u8> = vec![0; len];
|
||||
|
||||
self.read_memory_raw(addr, buf.as_mut_ptr() as *mut _, len)?;
|
||||
|
||||
if let Some(end) = buf.iter().position(|&x| x == 0) {
|
||||
buf.truncate(end);
|
||||
}
|
||||
|
||||
Ok(String::from_utf8(buf)?)
|
||||
}
|
||||
|
||||
/// Resolves a JMP/CALL instruction.
|
||||
pub fn resolve_jmp(
|
||||
&self,
|
||||
address: usize,
|
||||
addr: Address,
|
||||
offset: Option<usize>,
|
||||
length: Option<usize>,
|
||||
) -> Result<usize> {
|
||||
let displacement = self.read_memory::<i32>(address + offset.unwrap_or(0x1))?;
|
||||
len: Option<usize>,
|
||||
) -> Result<Address> {
|
||||
let disp = self.read_memory::<i32>(addr + offset.unwrap_or(0x1))?;
|
||||
|
||||
Ok(((address + length.unwrap_or(0x5)) as isize + displacement as isize) as usize)
|
||||
Ok(((addr.add(len.unwrap_or(0x5)).0 as isize + disp as isize) as usize).into())
|
||||
}
|
||||
|
||||
/// Resolves a RIP-relative address.
|
||||
pub fn resolve_rip(
|
||||
&self,
|
||||
address: usize,
|
||||
addr: Address,
|
||||
offset: Option<usize>,
|
||||
length: Option<usize>,
|
||||
) -> Result<usize> {
|
||||
let displacement = self.read_memory::<i32>(address + offset.unwrap_or(0x3))?;
|
||||
len: Option<usize>,
|
||||
) -> Result<Address> {
|
||||
let disp = self.read_memory::<i32>(addr + offset.unwrap_or(0x3))?;
|
||||
|
||||
Ok(((address + length.unwrap_or(0x7)) as isize + displacement as isize) as usize)
|
||||
Ok(((addr.add(len.unwrap_or(0x7)).0 as isize + disp as isize) as usize).into())
|
||||
}
|
||||
|
||||
/// Returns the process ID of a process by name.
|
||||
fn get_process_id_by_name(process_name: &str) -> Result<u32> {
|
||||
let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) }?;
|
||||
|
||||
@@ -215,9 +233,10 @@ impl Process {
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::ProcessNotFound(process_name.to_owned()))
|
||||
bail!("Process not found: {}", process_name)
|
||||
}
|
||||
|
||||
/// Converts a pattern to a list of bytes.
|
||||
fn pattern_to_bytes(pattern: &str) -> Vec<i32> {
|
||||
let mut bytes = Vec::new();
|
||||
|
||||
@@ -241,9 +260,7 @@ impl Process {
|
||||
}
|
||||
|
||||
if i + 1 < chars.len() {
|
||||
let value = i32::from_str_radix(&pattern[i..i + 2], 16);
|
||||
|
||||
match value {
|
||||
match i32::from_str_radix(&pattern[i..i + 2], 16) {
|
||||
Ok(v) => bytes.push(v),
|
||||
Err(_) => {}
|
||||
}
|
||||
@@ -259,6 +276,7 @@ impl Process {
|
||||
}
|
||||
|
||||
impl Drop for Process {
|
||||
/// Closes the process handle.
|
||||
fn drop(&mut self) {
|
||||
if !self.process_handle.is_invalid() {
|
||||
unsafe { CloseHandle(self.process_handle).unwrap() }
|
||||
|
@@ -1,7 +1,9 @@
|
||||
use std::ffi::c_char;
|
||||
use std::mem::offset_of;
|
||||
|
||||
use crate::error::Result;
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::mem::Address;
|
||||
use crate::remote::Process;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -13,21 +15,29 @@ pub struct InterfaceReg {
|
||||
}
|
||||
|
||||
impl InterfaceReg {
|
||||
pub fn ptr(&self, process: &Process) -> Result<usize> {
|
||||
/// Returns the pointer of the interface.
|
||||
pub fn pointer(&self, process: &Process) -> Result<Address> {
|
||||
process
|
||||
.read_memory::<usize>(self as *const _ as usize + offset_of!(InterfaceReg, create_fn))
|
||||
.read_memory::<usize>(
|
||||
(self as *const _ as usize + offset_of!(InterfaceReg, create_fn)).into(),
|
||||
)
|
||||
.map(|ptr| ptr.into())
|
||||
}
|
||||
|
||||
/// Returns the name of the interface.
|
||||
/// E.g. "Source2Client002"
|
||||
pub fn name(&self, process: &Process) -> Result<String> {
|
||||
let name_ptr = process
|
||||
.read_memory::<usize>(self as *const _ as usize + offset_of!(InterfaceReg, name))?;
|
||||
let name_ptr = process.read_memory::<usize>(
|
||||
(self as *const _ as usize + offset_of!(InterfaceReg, name)).into(),
|
||||
)?;
|
||||
|
||||
process.read_string(name_ptr)
|
||||
process.read_string(name_ptr.into())
|
||||
}
|
||||
|
||||
/// Returns the next interface in the list.
|
||||
pub fn next(&self, process: &Process) -> Result<*mut InterfaceReg> {
|
||||
process.read_memory::<*mut InterfaceReg>(
|
||||
self as *const _ as usize + offset_of!(InterfaceReg, next),
|
||||
(self as *const _ as usize + offset_of!(InterfaceReg, next)).into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@@ -1,31 +1,40 @@
|
||||
use crate::error::Result;
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::mem::Address;
|
||||
use crate::remote::Process;
|
||||
|
||||
use super::SchemaType;
|
||||
|
||||
/// Represents a class field in a schema.
|
||||
pub struct SchemaClassFieldData<'a> {
|
||||
process: &'a Process,
|
||||
address: usize,
|
||||
|
||||
/// Address of the class field.
|
||||
addr: Address,
|
||||
}
|
||||
|
||||
impl<'a> SchemaClassFieldData<'a> {
|
||||
pub fn new(process: &'a Process, address: usize) -> Self {
|
||||
Self { process, address }
|
||||
pub fn new(process: &'a Process, addr: Address) -> Self {
|
||||
Self { process, addr }
|
||||
}
|
||||
|
||||
/// Returns the name of the field.
|
||||
pub fn name(&self) -> Result<String> {
|
||||
let name_ptr = self.process.read_memory::<usize>(self.address)?;
|
||||
let name_ptr = self.process.read_memory::<usize>(self.addr + 0x0)?;
|
||||
|
||||
self.process.read_string(name_ptr)
|
||||
self.process.read_string_len(name_ptr.into(), 64)
|
||||
}
|
||||
|
||||
/// Returns the type of the field.
|
||||
pub fn r#type(&self) -> Result<SchemaType> {
|
||||
let type_ptr = self.process.read_memory::<usize>(self.address + 0x8)?;
|
||||
|
||||
Ok(SchemaType::new(self.process, type_ptr))
|
||||
Ok(SchemaType::new(
|
||||
self.process,
|
||||
self.process.read_memory::<usize>(self.addr + 0x8)?.into(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Returns the offset of the field.
|
||||
pub fn offset(&self) -> Result<u16> {
|
||||
self.process.read_memory::<u16>(self.address + 0x10)
|
||||
self.process.read_memory::<u16>(self.addr + 0x10)
|
||||
}
|
||||
}
|
||||
|
@@ -1,59 +1,76 @@
|
||||
use crate::error::Result;
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::mem::Address;
|
||||
use crate::remote::Process;
|
||||
|
||||
use super::SchemaClassFieldData;
|
||||
|
||||
/// Represents a class in a schema.
|
||||
pub struct SchemaClassInfo<'a> {
|
||||
process: &'a Process,
|
||||
address: usize,
|
||||
|
||||
/// Address of the class.
|
||||
addr: Address,
|
||||
|
||||
/// Name of the class.
|
||||
class_name: String,
|
||||
}
|
||||
|
||||
impl<'a> SchemaClassInfo<'a> {
|
||||
pub fn new(process: &'a Process, address: usize, class_name: &str) -> Self {
|
||||
pub fn new(process: &'a Process, addr: Address, class_name: &str) -> Self {
|
||||
Self {
|
||||
process,
|
||||
address,
|
||||
addr,
|
||||
class_name: class_name.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the name of the class.
|
||||
#[inline]
|
||||
pub fn name(&self) -> &str {
|
||||
&self.class_name
|
||||
}
|
||||
|
||||
/// Returns a list of fields in the class.
|
||||
pub fn fields(&self) -> Result<Vec<SchemaClassFieldData>> {
|
||||
let addr = self.process.read_memory::<usize>(self.addr + 0x28)?;
|
||||
|
||||
if addr == 0 {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let count = self.fields_count()?;
|
||||
|
||||
let base_address = self.process.read_memory::<usize>(self.address + 0x28)?;
|
||||
|
||||
let fields: Vec<SchemaClassFieldData> = (0..count as usize)
|
||||
.map(|i| base_address + (i * 0x20))
|
||||
.filter_map(|address| {
|
||||
if address != 0 {
|
||||
Some(SchemaClassFieldData::new(self.process, address))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
let fields: Vec<SchemaClassFieldData> = (addr..addr + count as usize * 0x20)
|
||||
.step_by(0x20)
|
||||
.map(|address| SchemaClassFieldData::new(self.process, address.into()))
|
||||
.collect();
|
||||
|
||||
Ok(fields)
|
||||
}
|
||||
|
||||
/// Returns the number of fields in the class.
|
||||
pub fn fields_count(&self) -> Result<u16> {
|
||||
self.process.read_memory::<u16>(self.address + 0x1C)
|
||||
self.process.read_memory::<u16>(self.addr + 0x1C)
|
||||
}
|
||||
|
||||
/// Returns the parent class.
|
||||
pub fn parent(&self) -> Result<Option<SchemaClassInfo>> {
|
||||
let addr = self.process.read_memory::<u64>(self.address + 0x38)?;
|
||||
let addr = self.process.read_memory::<usize>(self.addr + 0x38)?;
|
||||
|
||||
if addr == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let parent = self.process.read_memory::<u64>(addr as usize + 0x8)?;
|
||||
let name = self.process.read_string(self.process.read_memory::<usize>(parent as usize + 0x8)?)?;
|
||||
Ok(Some(SchemaClassInfo::new(self.process, parent as usize, &name)))
|
||||
let parent = self.process.read_memory::<usize>((addr + 0x8).into())?;
|
||||
|
||||
let name_ptr = self.process.read_memory::<usize>((parent + 0x8).into())?;
|
||||
let name = self.process.read_string(name_ptr.into())?;
|
||||
|
||||
Ok(Some(SchemaClassInfo::new(
|
||||
self.process,
|
||||
parent.into(),
|
||||
&name,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
@@ -1,42 +1,53 @@
|
||||
use std::mem;
|
||||
|
||||
use crate::error::Result;
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
use crate::mem::Address;
|
||||
use crate::remote::Process;
|
||||
|
||||
use super::SchemaSystemTypeScope;
|
||||
|
||||
/// Represents the schema system.
|
||||
pub struct SchemaSystem<'a> {
|
||||
process: &'a Process,
|
||||
address: usize,
|
||||
|
||||
/// Address of the schema system.
|
||||
addr: Address,
|
||||
}
|
||||
|
||||
impl<'a> SchemaSystem<'a> {
|
||||
pub fn new(process: &'a Process) -> Result<Self> {
|
||||
let mut address = process.find_pattern(
|
||||
let mut addr = 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_rip(address, None, None)?;
|
||||
addr = process.resolve_rip(addr, None, None)?;
|
||||
|
||||
Ok(Self { process, address })
|
||||
Ok(Self { process, addr })
|
||||
}
|
||||
|
||||
/// Returns a list of type scopes.
|
||||
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 size = self.process.read_memory::<u32>(self.addr + 0x190)?;
|
||||
|
||||
if size == 0 {
|
||||
bail!("Type scopes size is 0");
|
||||
}
|
||||
|
||||
let data = self.process.read_memory::<usize>(self.addr + 0x198)?;
|
||||
|
||||
let mut addresses: Vec<usize> = vec![0; size as usize];
|
||||
|
||||
self.process.read_memory_raw(
|
||||
data,
|
||||
data.into(),
|
||||
addresses.as_mut_ptr() as *mut _,
|
||||
addresses.len() * mem::size_of::<usize>(),
|
||||
)?;
|
||||
|
||||
let type_scopes: Vec<SchemaSystemTypeScope> = addresses
|
||||
.iter()
|
||||
.map(|&address| SchemaSystemTypeScope::new(self.process, address))
|
||||
.map(|&addr| SchemaSystemTypeScope::new(self.process, addr.into()))
|
||||
.collect();
|
||||
|
||||
Ok(type_scopes)
|
||||
|
@@ -1,42 +1,49 @@
|
||||
use crate::error::Result;
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::mem::Address;
|
||||
use crate::remote::Process;
|
||||
|
||||
use super::{SchemaClassInfo, SchemaTypeDeclaredClass, UtlTsHash};
|
||||
|
||||
/// Represents a schema system type scope.
|
||||
pub struct SchemaSystemTypeScope<'a> {
|
||||
process: &'a Process,
|
||||
address: usize,
|
||||
|
||||
/// Address of the schema system type scope.
|
||||
addr: Address,
|
||||
}
|
||||
|
||||
impl<'a> SchemaSystemTypeScope<'a> {
|
||||
pub fn new(process: &'a Process, address: usize) -> Self {
|
||||
Self { process, address }
|
||||
pub fn new(process: &'a Process, addr: Address) -> Self {
|
||||
Self { process, addr }
|
||||
}
|
||||
|
||||
/// Returns a list of classes in the type scope.
|
||||
pub fn classes(&self) -> Result<Vec<SchemaClassInfo>> {
|
||||
let classes = self
|
||||
let declared_classes = self
|
||||
.process
|
||||
.read_memory::<UtlTsHash<*mut SchemaTypeDeclaredClass>>(self.address + 0x588)?;
|
||||
.read_memory::<UtlTsHash<*mut SchemaTypeDeclaredClass>>(self.addr + 0x588)?;
|
||||
|
||||
let classes: Vec<SchemaClassInfo> = classes
|
||||
let classes: Vec<SchemaClassInfo> = declared_classes
|
||||
.elements(self.process)?
|
||||
.iter()
|
||||
.filter_map(|&address| {
|
||||
let address = address as usize;
|
||||
.filter_map(|&addr| {
|
||||
let addr = Address::from(addr as usize);
|
||||
|
||||
let declared_class = SchemaTypeDeclaredClass::new(self.process, address);
|
||||
let declared_class = SchemaTypeDeclaredClass::new(self.process, addr);
|
||||
|
||||
declared_class
|
||||
.name()
|
||||
.ok()
|
||||
.map(|name| SchemaClassInfo::new(self.process, address, &name))
|
||||
.map(|name| SchemaClassInfo::new(self.process, addr, &name))
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(classes)
|
||||
}
|
||||
|
||||
/// Returns the name of the module that the type scope belongs to.
|
||||
pub fn module_name(&self) -> Result<String> {
|
||||
self.process.read_string(self.address + 0x8)
|
||||
self.process.read_string_len(self.addr + 0x8, 256)
|
||||
}
|
||||
}
|
||||
|
@@ -1,12 +1,15 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::mem::Address;
|
||||
use crate::remote::Process;
|
||||
|
||||
/// Map of type names to their C equivalents.
|
||||
const TYPE_MAP: &[(&'static str, &'static str)] = &[
|
||||
("uint8", "uint8_t"),
|
||||
("uint16", "uint16_t"),
|
||||
@@ -32,22 +35,26 @@ lazy_static! {
|
||||
};
|
||||
}
|
||||
|
||||
/// Represents a schema type.
|
||||
pub struct SchemaType<'a> {
|
||||
process: &'a Process,
|
||||
address: usize,
|
||||
|
||||
/// Address of the schema type.
|
||||
addr: Address,
|
||||
}
|
||||
|
||||
impl<'a> SchemaType<'a> {
|
||||
pub fn new(process: &'a Process, address: usize) -> Self {
|
||||
Self { process, address }
|
||||
pub fn new(process: &'a Process, addr: Address) -> Self {
|
||||
Self { process, addr }
|
||||
}
|
||||
|
||||
/// Returns the name of the type.
|
||||
pub fn name(&self) -> Result<String> {
|
||||
let name_ptr = self.process.read_memory::<usize>(self.address + 0x8)?;
|
||||
let name_ptr = self.process.read_memory::<usize>(self.addr + 0x8)?;
|
||||
|
||||
let name = self
|
||||
.process
|
||||
.read_string(name_ptr)?
|
||||
.read_string(name_ptr.into())?
|
||||
.replace(" ", "")
|
||||
.to_string();
|
||||
|
||||
|
@@ -1,19 +1,25 @@
|
||||
use crate::error::Result;
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::mem::Address;
|
||||
use crate::remote::Process;
|
||||
|
||||
/// Represents a schema type declared class.
|
||||
pub struct SchemaTypeDeclaredClass<'a> {
|
||||
process: &'a Process,
|
||||
address: usize,
|
||||
|
||||
/// Address of the schema type declared class.
|
||||
addr: Address,
|
||||
}
|
||||
|
||||
impl<'a> SchemaTypeDeclaredClass<'a> {
|
||||
pub fn new(process: &'a Process, address: usize) -> Self {
|
||||
Self { process, address }
|
||||
pub fn new(process: &'a Process, addr: Address) -> Self {
|
||||
Self { process, addr }
|
||||
}
|
||||
|
||||
/// Returns the name of the class.
|
||||
pub fn name(&self) -> Result<String> {
|
||||
let name_ptr = self.process.read_memory::<usize>(self.address + 0x8)?;
|
||||
let name_ptr = self.process.read_memory::<usize>(self.addr + 0x8)?;
|
||||
|
||||
self.process.read_string(name_ptr)
|
||||
self.process.read_string_len(name_ptr.into(), 64)
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,10 @@
|
||||
use anyhow::Result;
|
||||
|
||||
use std::mem::offset_of;
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::remote::Process;
|
||||
|
||||
/// Represents a hash bucket.
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct HashFixedDataInternal<T, K> {
|
||||
@@ -14,11 +16,12 @@ pub struct HashFixedDataInternal<T, K> {
|
||||
impl<T, K> HashFixedDataInternal<T, K> {
|
||||
pub fn next(&self, process: &Process) -> Result<*mut HashFixedDataInternal<T, K>> {
|
||||
process.read_memory::<*mut HashFixedDataInternal<T, K>>(
|
||||
(self as *const _ as usize) + offset_of!(HashFixedDataInternal<T, K>, next),
|
||||
(self as *const _ as usize + offset_of!(HashFixedDataInternal<T, K>, next)).into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a hash bucket.
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct HashBucketDataInternal<T, K> {
|
||||
@@ -30,11 +33,12 @@ pub struct HashBucketDataInternal<T, K> {
|
||||
impl<T, K> HashBucketDataInternal<T, K> {
|
||||
pub fn next(&self, process: &Process) -> Result<*mut HashFixedDataInternal<T, K>> {
|
||||
process.read_memory::<*mut HashFixedDataInternal<T, K>>(
|
||||
(self as *const _ as usize) + offset_of!(HashBucketDataInternal<T, K>, next),
|
||||
(self as *const _ as usize + offset_of!(HashBucketDataInternal<T, K>, next)).into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a hash table.
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct HashAllocatedData<T, K> {
|
||||
@@ -45,11 +49,12 @@ pub struct HashAllocatedData<T, K> {
|
||||
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) + offset_of!(HashAllocatedData<T, K>, list),
|
||||
(self as *const _ as usize + offset_of!(HashAllocatedData<T, K>, list)).into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a hash table.
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct HashUnallocatedData<T, K> {
|
||||
@@ -63,23 +68,24 @@ pub struct HashUnallocatedData<T, K> {
|
||||
impl<T, K> HashUnallocatedData<T, K> {
|
||||
pub fn next(&self, process: &Process) -> Result<*mut HashUnallocatedData<T, K>> {
|
||||
process.read_memory::<*mut HashUnallocatedData<T, K>>(
|
||||
self as *const _ as usize + offset_of!(HashUnallocatedData<T, K>, next),
|
||||
(self as *const _ as usize + offset_of!(HashUnallocatedData<T, K>, next)).into(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn ui_key(&self, process: &Process) -> Result<K> {
|
||||
process.read_memory::<K>(
|
||||
(self as *const _ as usize) + offset_of!(HashUnallocatedData<T, K>, ui_key),
|
||||
(self as *const _ as usize + offset_of!(HashUnallocatedData<T, K>, ui_key)).into(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn block_list(&self, process: &Process) -> Result<[HashBucketDataInternal<T, K>; 256]> {
|
||||
process.read_memory::<[HashBucketDataInternal<T, K>; 256]>(
|
||||
(self as *const _ as usize) + offset_of!(HashUnallocatedData<T, K>, block_list),
|
||||
(self as *const _ as usize + offset_of!(HashUnallocatedData<T, K>, block_list)).into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a hash bucket.
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct HashBucket<T, K> {
|
||||
@@ -100,17 +106,20 @@ pub struct UtlMemoryPool {
|
||||
}
|
||||
|
||||
impl UtlMemoryPool {
|
||||
/// Returns the number of blocks per blob.
|
||||
#[inline]
|
||||
pub fn block_size(&self) -> i32 {
|
||||
self.blocks_per_blob
|
||||
}
|
||||
|
||||
/// Returns the number of blocks allocated.
|
||||
#[inline]
|
||||
pub fn count(&self) -> i32 {
|
||||
self.block_allocated_size
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a thread-safe hash table.
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct UtlTsHash<T, K = u64> {
|
||||
@@ -122,16 +131,19 @@ impl<T, K> UtlTsHash<T, K>
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
/// Returns the number of blocks per blob.
|
||||
#[inline]
|
||||
pub fn block_size(&self) -> i32 {
|
||||
self.entry_memory.block_size()
|
||||
}
|
||||
|
||||
/// Returns the number of blocks allocated.
|
||||
#[inline]
|
||||
pub fn count(&self) -> i32 {
|
||||
self.entry_memory.count()
|
||||
}
|
||||
|
||||
/// Returns a list of elements in the hash table.
|
||||
pub fn elements(&self, process: &Process) -> Result<Vec<T>> {
|
||||
let min_size = (self.block_size() as usize).min(self.count() as usize);
|
||||
|
||||
|
Reference in New Issue
Block a user