* Updated for memflow 0.2.2
* Replaced periods with underscores in generated file names for easier inclusion
* Program execution now continues if analysis fails at any point
* Removed custom error type in favor of anyhow
* Added logging to cs2-dumper.log
* Now compilable on Linux
This commit is contained in:
a2x
2024-07-30 02:06:35 +10:00
parent 8897183075
commit 7933103b03
98 changed files with 8413 additions and 8332 deletions

View File

@@ -1,3 +1,7 @@
use std::collections::BTreeMap;
use anyhow::{bail, Result};
use log::debug;
use memflow::prelude::v1::*;
@@ -5,20 +9,16 @@ use memflow::prelude::v1::*;
use pelite::pattern;
use pelite::pe64::{Pe, PeView};
use serde::{Deserialize, Serialize};
use crate::error::{Error, Result};
use crate::source2::KeyButton;
#[derive(Debug, Deserialize, Serialize)]
pub struct Button {
pub name: String,
pub value: u32,
}
pub type ButtonMap = BTreeMap<String, imem>;
pub fn buttons(process: &mut IntoProcessInstanceArcBox<'_>) -> Result<Vec<Button>> {
pub fn buttons(process: &mut IntoProcessInstanceArcBox<'_>) -> Result<ButtonMap> {
let module = process.module_by_name("client.dll")?;
let buf = process.read_raw(module.base, module.size as _)?;
let buf = process
.read_raw(module.base, module.size as _)
.data_part()?;
let view = PeView::from_bytes(&buf)?;
@@ -28,7 +28,7 @@ pub fn buttons(process: &mut IntoProcessInstanceArcBox<'_>) -> Result<Vec<Button
.scanner()
.finds_code(pattern!("488b15${'} 4885d2 74? 0f1f40"), &mut save)
{
return Err(Error::Other("unable to find button list pattern"));
bail!("outdated button list pattern");
}
read_buttons(process, &module, module.base + save[1])
@@ -38,32 +38,28 @@ fn read_buttons(
process: &mut IntoProcessInstanceArcBox<'_>,
module: &ModuleInfo,
list_addr: Address,
) -> Result<Vec<Button>> {
let mut buttons = Vec::new();
) -> Result<ButtonMap> {
let mut map = ButtonMap::new();
let mut cur_button = Pointer64::<KeyButton>::from(process.read_addr64(list_addr)?);
let mut cur_button = Pointer64::<KeyButton>::from(process.read_addr64(list_addr).data_part()?);
while !cur_button.is_null() {
let button = cur_button.read(process)?;
let name = button.name.read_string(process)?.to_string();
let value =
((cur_button.address() - module.base) + offset_of!(KeyButton.state) as i64) as u32;
let button = process.read_ptr(cur_button).data_part()?;
let name = process.read_utf8(button.name.address(), 32).data_part()?;
let rva = (cur_button.address() - module.base) + offset_of!(KeyButton.state) as imem;
debug!(
"found button: {} @ {:#X} ({} + {:#X})",
"found button: {} at {:#X} ({} + {:#X})",
name,
value as u64 + module.base.to_umem(),
cur_button.to_umem() + offset_of!(KeyButton.state) as umem,
module.name,
value
rva
);
buttons.push(Button { name, value });
map.insert(name, rva);
cur_button = button.next;
}
buttons.sort_unstable_by(|a, b| a.name.cmp(&b.name));
Ok(buttons)
Ok(map)
}

View File

@@ -1,5 +1,7 @@
use std::collections::BTreeMap;
use anyhow::Result;
use log::debug;
use memflow::prelude::v1::*;
@@ -7,25 +9,20 @@ use memflow::prelude::v1::*;
use pelite::pe64::exports::Export;
use pelite::pe64::{Pe, PeView};
use serde::{Deserialize, Serialize};
use crate::error::Result;
use crate::mem::read_addr64_rip;
use crate::source2::InterfaceReg;
pub type InterfaceMap = BTreeMap<String, Vec<Interface>>;
#[derive(Debug, Deserialize, Serialize)]
pub struct Interface {
pub name: String,
pub value: u32,
}
pub type InterfaceMap = BTreeMap<String, BTreeMap<String, imem>>;
pub fn interfaces(process: &mut IntoProcessInstanceArcBox<'_>) -> Result<InterfaceMap> {
process
.module_list()?
.iter()
.filter_map(|module| {
let buf = process.read_raw(module.base, module.size as _).ok()?;
let buf = process
.read_raw(module.base, module.size as _)
.data_part()
.ok()?;
let view = PeView::from_bytes(&buf).ok()?;
@@ -38,8 +35,7 @@ pub fn interfaces(process: &mut IntoProcessInstanceArcBox<'_>) -> Result<Interfa
.ok()?;
if let Export::Symbol(symbol) = ci_export {
let list_addr = process
.read_addr64_rip(module.base + symbol)
let list_addr = read_addr64_rip(process, module.base + symbol)
.data_part()
.ok()?;
@@ -58,32 +54,29 @@ fn read_interfaces(
process: &mut IntoProcessInstanceArcBox<'_>,
module: &ModuleInfo,
list_addr: Address,
) -> Result<Vec<Interface>> {
let mut ifaces = Vec::new();
) -> Result<BTreeMap<String, imem>> {
let mut ifaces = BTreeMap::new();
let mut cur_reg = Pointer64::<InterfaceReg>::from(process.read_addr64(list_addr)?);
let mut cur_reg = Pointer64::<InterfaceReg>::from(process.read_addr64(list_addr).data_part()?);
while !cur_reg.is_null() {
let reg = cur_reg.read(process)?;
let name = reg.name.read_string(process)?.to_string();
let instance = process.read_addr64_rip(reg.create_fn.address())?;
let value = (instance - module.base) as u32;
let reg = process.read_ptr(cur_reg).data_part()?;
let name = process.read_utf8(reg.name.address(), 4096).data_part()?;
let instance = read_addr64_rip(process, reg.create_fn.address())?;
let value = instance - module.base;
debug!(
"found interface: {} @ {:#X} ({} + {:#X})",
"found interface: {} at {:#X} ({} + {:#X})",
name,
value as u64 + module.base.to_umem(),
module.name,
value
);
ifaces.push(Interface { name, value });
ifaces.insert(name, value);
cur_reg = reg.next;
}
ifaces.sort_unstable_by(|a, b| a.name.cmp(&b.name));
Ok(ifaces)
}

View File

@@ -3,27 +3,69 @@ pub use interfaces::*;
pub use offsets::*;
pub use schemas::*;
use memflow::prelude::v1::*;
use std::any::type_name;
use crate::error::Result;
use anyhow::Result;
use log::{error, info};
use memflow::prelude::v1::*;
mod buttons;
mod interfaces;
mod offsets;
mod schemas;
#[derive(Debug)]
pub struct AnalysisResult {
pub buttons: Vec<Button>,
pub buttons: ButtonMap,
pub interfaces: InterfaceMap,
pub offsets: OffsetMap,
pub schemas: SchemaMap,
}
pub fn analyze_all(process: &mut IntoProcessInstanceArcBox<'_>) -> Result<AnalysisResult> {
let buttons = buttons(process)?;
let interfaces = interfaces(process)?;
let offsets = offsets(process)?;
let schemas = schemas(process)?;
let buttons = analyze(process, buttons);
info!("found {} buttons", buttons.len());
let interfaces = analyze(process, interfaces);
info!(
"found {} interfaces across {} modules",
interfaces
.iter()
.map(|(_, ifaces)| ifaces.len())
.sum::<usize>(),
interfaces.len()
);
let offsets = analyze(process, offsets);
info!(
"found {} offsets across {} modules",
offsets
.iter()
.map(|(_, offsets)| offsets.len())
.sum::<usize>(),
offsets.len()
);
let schemas = analyze(process, schemas);
let (class_count, enum_count) =
schemas
.values()
.fold((0, 0), |(classes, enums), (class_vec, enum_vec)| {
(classes + class_vec.len(), enums + enum_vec.len())
});
info!(
"found {} classes and {} enums across {} modules",
class_count,
enum_count,
schemas.len()
);
Ok(AnalysisResult {
buttons,
@@ -32,3 +74,21 @@ pub fn analyze_all(process: &mut IntoProcessInstanceArcBox<'_>) -> Result<Analys
schemas,
})
}
#[inline]
fn analyze<F, T>(process: &mut IntoProcessInstanceArcBox<'_>, f: F) -> T
where
F: FnOnce(&mut IntoProcessInstanceArcBox<'_>) -> Result<T>,
T: Default,
{
let name = type_name::<F>();
match f(process) {
Ok(result) => result,
Err(err) => {
error!("failed to read {}: {}", name, err);
T::default()
}
}
}

View File

@@ -1,18 +1,18 @@
use std::collections::BTreeMap;
use anyhow::Result;
use log::{debug, error};
use memflow::prelude::v1::*;
use pelite::pattern;
use pelite::pattern::{save_len, Atom};
use pelite::pe64::{Pe, PeView};
use pelite::pe64::{Pe, PeView, Rva};
use phf::{phf_map, Map};
use crate::error::Result;
pub type OffsetMap = BTreeMap<String, BTreeMap<String, u32>>;
pub type OffsetMap = BTreeMap<String, BTreeMap<String, Rva>>;
macro_rules! pattern_map {
($($module:ident => {
@@ -26,20 +26,20 @@ macro_rules! pattern_map {
&'static str,
(
&'static [Atom],
Option<fn(&PeView, &mut BTreeMap<String, u32>, u32)>,
Option<fn(&PeView, &mut BTreeMap<String, Rva>, Rva)>,
),
> = phf_map! {
$($name => ($pattern, $($callback)?)),+
};
pub fn offsets(view: PeView<'_>) -> BTreeMap<String, u32> {
pub fn offsets(view: PeView<'_>) -> BTreeMap<String, Rva> {
let mut map = BTreeMap::new();
for (&name, (pat, callback)) in &PATTERNS {
let mut save = vec![0; save_len(pat)];
if !view.scanner().finds_code(pat, &mut save) {
error!("unable to find pattern: {}", name);
error!("outdated pattern: {}", name);
continue;
}
@@ -55,7 +55,7 @@ macro_rules! pattern_map {
for (name, value) in &map {
debug!(
"found offset: {} @ {:#X} ({}.dll + {:#X})",
"found offset: {} at {:#X} ({}.dll + {:#X})",
name,
*value as u64 + view.optional_header().ImageBase,
stringify!($module),
@@ -130,8 +130,11 @@ pattern_map! {
pub fn offsets(process: &mut IntoProcessInstanceArcBox<'_>) -> Result<OffsetMap> {
let mut map = BTreeMap::new();
let modules: [(&str, fn(PeView) -> BTreeMap<String, u32>); 5] = [
("client.dll", client::offsets),
let modules = [
(
"client.dll",
client::offsets as fn(PeView) -> BTreeMap<String, u32>,
),
("engine2.dll", engine2::offsets),
("inputsystem.dll", input_system::offsets),
("matchmaking.dll", matchmaking::offsets),
@@ -140,7 +143,10 @@ pub fn offsets(process: &mut IntoProcessInstanceArcBox<'_>) -> Result<OffsetMap>
for (module_name, offsets) in &modules {
let module = process.module_by_name(module_name)?;
let buf = process.read_raw(module.base, module.size as _)?;
let buf = process
.read_raw(module.base, module.size as _)
.data_part()?;
let view = PeView::from_bytes(&buf)?;
@@ -218,7 +224,7 @@ mod tests {
.read_addr64((global_vars + 0x1B8).into())
.data_part()?;
process.read_char_string(addr).data_part()?
process.read_utf8(addr, 4096).data_part()?
};
println!("current map name: {}", cur_map_name);
@@ -244,7 +250,7 @@ mod tests {
.data_part()?;
let player_name = process
.read_char_string((local_player_controller + player_name_offset).into())
.read_utf8((local_player_controller + player_name_offset).into(), 4096)
.data_part()?;
println!("local player name: {}", player_name);

View File

@@ -1,6 +1,8 @@
use std::collections::BTreeMap;
use std::ffi::CStr;
use anyhow::{bail, Result};
use log::debug;
use memflow::prelude::v1::*;
@@ -10,7 +12,6 @@ use pelite::pe64::{Pe, PeView};
use serde::{Deserialize, Serialize};
use crate::error::{Error, Result};
use crate::source2::*;
pub type SchemaMap = BTreeMap<String, (Vec<Class>, Vec<Enum>)>;
@@ -80,30 +81,34 @@ fn read_class_binding(
process: &mut IntoProcessInstanceArcBox<'_>,
binding_ptr: Pointer64<SchemaClassBinding>,
) -> Result<Class> {
let binding = binding_ptr.read(process)?;
let binding = process.read_ptr(binding_ptr).data_part()?;
let module_name = binding
.module_name
.read_string(process)
let module_name = process
.read_utf8(binding.module_name.address(), 4096)
.data_part()
.map(|s| format!("{}.dll", s))?;
let name = binding.name.read_string(process)?.to_string();
let name = process
.read_utf8(binding.name.address(), 4096)
.data_part()?;
if name.is_empty() {
return Err(Error::Other("empty class name"));
bail!("empty class name");
}
let parent = binding.base_classes.non_null().and_then(|ptr| {
let base_class = ptr.read(process).ok()?;
let parent_class = base_class.prev.read(process).ok()?;
let base_class = process.read_ptr(ptr).data_part().ok()?;
let parent_class = process.read_ptr(base_class.prev).data_part().ok()?;
let module_name = parent_class
.module_name
.read_string(process)
.ok()?
.to_string();
let module_name = process
.read_utf8(parent_class.module_name.address(), 4096)
.data_part()
.ok()?;
let name = parent_class.name.read_string(process).ok()?.to_string();
let name = process
.read_utf8(parent_class.name.address(), 4096)
.data_part()
.ok()?;
Some(Box::new(Class {
name,
@@ -118,11 +123,11 @@ fn read_class_binding(
let metadata = read_class_binding_metadata(process, &binding)?;
debug!(
"found class: {} @ {:#X} (module name: {}) (parent name: {:?}) (metadata count: {}) (fields count: {})",
"found class: {} at {:#X} (module name: {}) (parent name: {:?}) (metadata count: {}) (field count: {})",
name,
binding_ptr.to_umem(),
module_name,
parent.as_ref().map(|parent| parent.name.clone()),
parent.as_ref().map(|p| p.name.clone()),
metadata.len(),
fields.len(),
);
@@ -144,18 +149,21 @@ fn read_class_binding_fields(
return Ok(Vec::new());
}
(0..binding.fields_count).try_fold(Vec::new(), |mut acc, i| {
let field = binding.fields.at(i as _).read(process)?;
(0..binding.field_count).try_fold(Vec::new(), |mut acc, i| {
let field = process.read_ptr(binding.fields.at(i as _)).data_part()?;
if field.schema_type.is_null() {
return Ok(acc);
}
let name = field.name.read_string(process)?.to_string();
let schema_type = field.schema_type.read(process)?;
let name = process.read_utf8(field.name.address(), 4096).data_part()?;
let schema_type = process.read_ptr(field.schema_type).data_part()?;
// TODO: Parse this properly.
let type_name = schema_type.name.read_string(process)?.replace(" ", "");
let type_name = process
.read_utf8(schema_type.name.address(), 4096)
.data_part()?
.replace(" ", "");
acc.push(ClassField {
name,
@@ -176,30 +184,39 @@ fn read_class_binding_metadata(
}
(0..binding.static_metadata_count).try_fold(Vec::new(), |mut acc, i| {
let metadata = binding.static_metadata.at(i as _).read(process)?;
let metadata = process
.read_ptr(binding.static_metadata.at(i as _))
.data_part()?;
if metadata.network_value.is_null() {
return Ok(acc);
}
let name = metadata.name.read_string(process)?.to_string();
let network_value = metadata.network_value.read(process)?;
let name = process
.read_utf8(metadata.name.address(), 4096)
.data_part()?;
let network_value = process.read_ptr(metadata.network_value).data_part()?;
let metadata = match name.as_str() {
"MNetworkChangeCallback" => unsafe {
let name = network_value
.value
.name_ptr
.read_string(process)?
.to_string();
let name = process
.read_utf8(network_value.value.name_ptr.address(), 4096)
.data_part()?;
ClassMetadata::NetworkChangeCallback { name }
},
"MNetworkVarNames" => unsafe {
let var_value = network_value.value.var_value;
let name = var_value.name.read_string(process)?.to_string();
let type_name = var_value.type_name.read_string(process)?.replace(" ", "");
let name = process
.read_utf8(var_value.name.address(), 4096)
.data_part()?;
let type_name = process
.read_utf8(var_value.type_name.address(), 4096)
.data_part()?
.replace(" ", "");
ClassMetadata::NetworkVarNames { name, type_name }
},
@@ -216,17 +233,20 @@ fn read_enum_binding(
process: &mut IntoProcessInstanceArcBox<'_>,
binding_ptr: Pointer64<SchemaEnumBinding>,
) -> Result<Enum> {
let binding = binding_ptr.read(process)?;
let name = binding.name.read_string(process)?.to_string();
let binding = process.read_ptr(binding_ptr).data_part()?;
let name = process
.read_utf8(binding.name.address(), 4096)
.data_part()?;
if name.is_empty() {
return Err(Error::Other("empty enum name"));
bail!("empty enum name");
}
let members = read_enum_binding_members(process, &binding)?;
debug!(
"found enum: {} @ {:#X} (alignment: {}) (members count: {})",
"found enum: {} at {:#X} (alignment: {}) (member count: {})",
name,
binding_ptr.to_umem(),
binding.align_of,
@@ -236,7 +256,7 @@ fn read_enum_binding(
Ok(Enum {
name,
alignment: binding.align_of,
size: binding.enumerators_count,
size: binding.enumerator_count,
members,
})
}
@@ -249,9 +269,14 @@ fn read_enum_binding_members(
return Ok(Vec::new());
}
(0..binding.enumerators_count).try_fold(Vec::new(), |mut acc, i| {
let enumerator = binding.enumerators.at(i as _).read(process)?;
let name = enumerator.name.read_string(process)?.to_string();
(0..binding.enumerator_count).try_fold(Vec::new(), |mut acc, i| {
let enumerator = process
.read_ptr(binding.enumerators.at(i as _))
.data_part()?;
let name = process
.read_utf8(enumerator.name.address(), 4096)
.data_part()?;
acc.push(EnumMember {
name,
@@ -264,7 +289,10 @@ fn read_enum_binding_members(
fn read_schema_system(process: &mut IntoProcessInstanceArcBox<'_>) -> Result<SchemaSystem> {
let module = process.module_by_name("schemasystem.dll")?;
let buf = process.read_raw(module.base, module.size as _)?;
let buf = process
.read_raw(module.base, module.size as _)
.data_part()?;
let view = PeView::from_bytes(&buf)?;
@@ -274,13 +302,13 @@ fn read_schema_system(process: &mut IntoProcessInstanceArcBox<'_>) -> Result<Sch
.scanner()
.finds_code(pattern!("4c8d35${'} 0f2845"), &mut save)
{
return Err(Error::Other("unable to find schema system pattern"));
bail!("outdated schema system pattern");
}
let schema_system: SchemaSystem = process.read(module.base + save[1])?;
let schema_system: SchemaSystem = process.read(module.base + save[1]).data_part()?;
if schema_system.num_registrations == 0 {
return Err(Error::Other("no schema system registrations found"));
bail!("no schema system registrations found");
}
Ok(schema_system)
@@ -294,7 +322,7 @@ fn read_type_scopes(
(0..type_scopes.size).try_fold(Vec::new(), |mut acc, i| {
let type_scope_ptr = type_scopes.element(process, i as _)?;
let type_scope = type_scope_ptr.read(process)?;
let type_scope = process.read_ptr(type_scope_ptr).data_part()?;
let module_name = unsafe { CStr::from_ptr(type_scope.name.as_ptr()) }
.to_string_lossy()
@@ -319,7 +347,7 @@ fn read_type_scopes(
}
debug!(
"found type scope: {} @ {:#X} (classes count: {}) (enums count: {})",
"found type scope: {} at {:#X} (class count: {}) (enum count: {})",
module_name,
type_scope_ptr.to_umem(),
classes.len(),

View File

@@ -1,31 +0,0 @@
use thiserror::Error;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Error)]
pub enum Error {
#[error(transparent)]
Fmt(#[from] std::fmt::Error),
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Memflow(#[from] memflow::error::Error),
#[error(transparent)]
Pelite(#[from] pelite::Error),
#[error(transparent)]
Serde(#[from] serde_json::Error),
#[error("{0}")]
Other(&'static str),
}
impl<T> From<memflow::error::PartialError<T>> for Error {
#[inline]
fn from(err: memflow::error::PartialError<T>) -> Self {
Error::Memflow(err.into())
}
}

View File

@@ -1,20 +1,21 @@
use std::fs::File;
use std::path::PathBuf;
use std::str::FromStr;
use std::time::Instant;
use anyhow::Result;
use clap::*;
use log::{info, Level};
use log::{info, LevelFilter};
use memflow::prelude::v1::*;
use simplelog::{ColorChoice, Config, TermLogger, TerminalMode};
use simplelog::{ColorChoice, CombinedLogger, Config, TermLogger, TerminalMode, WriteLogger};
use error::Result;
use output::Output;
mod analysis;
mod error;
mod mem;
mod output;
mod source2;
@@ -53,22 +54,28 @@ struct Args {
fn main() -> Result<()> {
let args = Args::parse();
let now = Instant::now();
let log_level = match args.verbose {
0 => Level::Error,
1 => Level::Warn,
2 => Level::Info,
3 => Level::Debug,
_ => Level::Trace,
let level_filter = match args.verbose {
0 => LevelFilter::Error,
1 => LevelFilter::Warn,
2 => LevelFilter::Info,
3 => LevelFilter::Debug,
_ => LevelFilter::Trace,
};
TermLogger::init(
log_level.to_level_filter(),
Config::default(),
TerminalMode::Mixed,
ColorChoice::Auto,
)
CombinedLogger::init(vec![
TermLogger::new(
level_filter,
Config::default(),
TerminalMode::Mixed,
ColorChoice::Auto,
),
WriteLogger::new(
LevelFilter::Info,
Config::default(),
File::create("cs2-dumper.log").unwrap(),
),
])
.unwrap();
let conn_args = args
@@ -76,29 +83,43 @@ fn main() -> Result<()> {
.map(|s| ConnectorArgs::from_str(&s).expect("unable to parse connector arguments"))
.unwrap_or_default();
let os = if let Some(conn) = args.connector {
let inventory = Inventory::scan();
let os = match args.connector {
Some(conn) => {
let inventory = Inventory::scan();
inventory
.builder()
.connector(&conn)
.args(conn_args)
.os("win32")
.build()?
} else {
// Fallback to the native OS layer if no connector name was specified.
memflow_native::create_os(&OsArgs::default(), LibArc::default())?
inventory
.builder()
.connector(&conn)
.args(conn_args)
.os("win32")
.build()?
}
None => {
#[cfg(windows)]
{
memflow_native::create_os(&OsArgs::default(), LibArc::default())?
}
#[cfg(not(windows))]
{
panic!("no connector specified")
}
}
};
let mut process = os.into_process_by_name(&args.process_name)?;
let now = Instant::now();
let result = analysis::analyze_all(&mut process)?;
let output = Output::new(&args.file_types, args.indent_size, &args.output, &result)?;
output.dump_all(&mut process)?;
info!("finished in {:?}", now.elapsed());
info!(
"analysis completed in {} seconds",
now.elapsed().as_secs_f64()
);
Ok(())
}

View File

@@ -1,4 +1,4 @@
use memflow::types::{Pointer, PrimitiveAddress};
use memflow::prelude::v1::*;
pub trait PointerExt {
fn is_null(&self) -> bool;
@@ -10,3 +10,15 @@ impl<U: PrimitiveAddress, T> PointerExt for Pointer<U, T> {
self.inner.is_null()
}
}
pub fn read_addr64_rip(
process: &mut IntoProcessInstanceArcBox<'_>,
addr: Address,
) -> PartialResult<Address> {
let displacement = match process.read::<i32>(addr + 0x3) {
Ok(d) => d,
Err(e) => return Err(PartialError::Error(e.into())),
};
Ok(addr + 0x7 + displacement)
}

View File

@@ -1,20 +1,16 @@
use std::collections::BTreeMap;
use std::fmt::{self, Write};
use super::{Button, CodeWriter, Formatter};
use super::{ButtonMap, CodeWriter, Formatter};
impl CodeWriter for Vec<Button> {
impl CodeWriter for ButtonMap {
fn write_cs(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
fmt.block("namespace CS2Dumper", false, |fmt| {
writeln!(fmt, "// Module: client.dll")?;
fmt.block("public static class Buttons", false, |fmt| {
for button in self {
writeln!(
fmt,
"public const nint {} = {:#X};",
button.name, button.value
)?;
for (name, value) in self {
writeln!(fmt, "public const nint {} = {:#X};", name, value)?;
}
Ok(())
@@ -30,12 +26,8 @@ impl CodeWriter for Vec<Button> {
writeln!(fmt, "// Module: client.dll")?;
fmt.block("namespace buttons", false, |fmt| {
for button in self {
writeln!(
fmt,
"constexpr std::ptrdiff_t {} = {:#X};",
button.name, button.value
)?;
for (name, value) in self {
writeln!(fmt, "constexpr std::ptrdiff_t {} = {:#X};", name, value)?;
}
Ok(())
@@ -45,15 +37,12 @@ impl CodeWriter for Vec<Button> {
fn write_json(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
let content = {
let buttons: BTreeMap<_, _> = self
.iter()
.map(|button| (button.name.as_str(), button.value))
.collect();
let buttons: BTreeMap<_, _> = self.iter().map(|(name, value)| (name, value)).collect();
BTreeMap::from_iter([("client.dll", buttons)])
};
fmt.write_str(&serde_json::to_string_pretty(&content).expect("unable to serialize json"))
fmt.write_str(&serde_json::to_string_pretty(&content).unwrap())
}
fn write_rs(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
@@ -63,14 +52,14 @@ impl CodeWriter for Vec<Button> {
writeln!(fmt, "// Module: client.dll")?;
fmt.block("pub mod buttons", false, |fmt| {
for button in self {
let mut name = button.name.clone();
for (name, value) in self {
let mut name = name.clone();
if name == "use" {
name = format!("r#{}", name);
}
writeln!(fmt, "pub const {}: usize = {:#X};", name, button.value)?;
writeln!(fmt, "pub const {}: usize = {:#X};", name, value)?;
}
Ok(())

View File

@@ -15,12 +15,8 @@ impl CodeWriter for InterfaceMap {
&format!("public static class {}", AsPascalCase(slugify(module_name))),
false,
|fmt| {
for iface in ifaces {
writeln!(
fmt,
"public const nint {} = {:#X};",
iface.name, iface.value
)?;
for (name, value) in ifaces {
writeln!(fmt, "public const nint {} = {:#X};", name, value)?;
}
Ok(())
@@ -45,12 +41,8 @@ impl CodeWriter for InterfaceMap {
&format!("namespace {}", AsSnakeCase(slugify(module_name))),
false,
|fmt| {
for iface in ifaces {
writeln!(
fmt,
"constexpr std::ptrdiff_t {} = {:#X};",
iface.name, iface.value
)?;
for (name, value) in ifaces {
writeln!(fmt, "constexpr std::ptrdiff_t {} = {:#X};", name, value)?;
}
Ok(())
@@ -67,16 +59,14 @@ impl CodeWriter for InterfaceMap {
let content: BTreeMap<_, _> = self
.iter()
.map(|(module_name, ifaces)| {
let ifaces: BTreeMap<_, _> = ifaces
.iter()
.map(|iface| (&iface.name, iface.value))
.collect();
let ifaces: BTreeMap<_, _> =
ifaces.iter().map(|(name, value)| (name, value)).collect();
(module_name, ifaces)
})
.collect();
fmt.write_str(&serde_json::to_string_pretty(&content).expect("unable to serialize json"))
fmt.write_str(&serde_json::to_string_pretty(&content).unwrap())
}
fn write_rs(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
@@ -91,12 +81,8 @@ impl CodeWriter for InterfaceMap {
&format!("pub mod {}", AsSnakeCase(slugify(module_name))),
false,
|fmt| {
for iface in ifaces {
writeln!(
fmt,
"pub const {}: usize = {:#X};",
iface.name, iface.value
)?;
for (name, value) in ifaces {
writeln!(fmt, "pub const {}: usize = {:#X};", name, value)?;
}
Ok(())

View File

@@ -2,6 +2,8 @@ use std::fmt::{self, Write};
use std::fs;
use std::path::Path;
use anyhow::{anyhow, Result};
use chrono::{DateTime, Utc};
use memflow::prelude::v1::*;
@@ -11,7 +13,6 @@ use serde_json::json;
use formatter::Formatter;
use crate::analysis::*;
use crate::error::{Error, Result};
mod buttons;
mod formatter;
@@ -20,13 +21,14 @@ mod offsets;
mod schemas;
enum Item<'a> {
Buttons(&'a Vec<Button>),
Buttons(&'a ButtonMap),
Interfaces(&'a InterfaceMap),
Offsets(&'a OffsetMap),
Schemas(&'a SchemaMap),
}
impl<'a> Item<'a> {
#[inline]
fn write(&self, fmt: &mut Formatter<'a>, file_type: &str) -> fmt::Result {
match file_type {
"cs" => self.write_cs(fmt),
@@ -46,6 +48,7 @@ trait CodeWriter {
}
impl<'a> CodeWriter for Item<'a> {
#[inline]
fn write_cs(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
match self {
Item::Buttons(buttons) => buttons.write_cs(fmt),
@@ -55,6 +58,7 @@ impl<'a> CodeWriter for Item<'a> {
}
}
#[inline]
fn write_hpp(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
match self {
Item::Buttons(buttons) => buttons.write_hpp(fmt),
@@ -64,6 +68,7 @@ impl<'a> CodeWriter for Item<'a> {
}
}
#[inline]
fn write_json(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
match self {
Item::Buttons(buttons) => buttons.write_json(fmt),
@@ -73,6 +78,7 @@ impl<'a> CodeWriter for Item<'a> {
}
}
#[inline]
fn write_rs(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
match self {
Item::Buttons(buttons) => buttons.write_rs(fmt),
@@ -137,9 +143,9 @@ impl<'a> Output<'a> {
let module = process.module_by_name(module_name).ok()?;
let offset = offsets.iter().find(|(name, _)| *name == "dwBuildNumber")?.1;
process.read::<u32>(module.base + offset).ok()
process.read::<u32>(module.base + offset).data_part().ok()
})
.ok_or(Error::Other("unable to read build number"))?;
.ok_or(anyhow!("failed to read build number"))?;
let content = serde_json::to_string_pretty(&json!({
"timestamp": self.timestamp.to_rfc3339(),
@@ -174,7 +180,7 @@ impl<'a> Output<'a> {
for (module_name, (classes, enums)) in &self.result.schemas {
let map = SchemaMap::from([(module_name.clone(), (classes.clone(), enums.clone()))]);
self.dump_item(module_name, &Item::Schemas(&map))?;
self.dump_item(&slugify(&module_name), &Item::Schemas(&map))?;
}
Ok(())
@@ -188,6 +194,7 @@ impl<'a> Output<'a> {
}
}
#[inline]
fn slugify(input: &str) -> String {
input.replace(|c: char| !c.is_alphanumeric(), "_")
}

View File

@@ -55,7 +55,7 @@ impl CodeWriter for OffsetMap {
}
fn write_json(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
fmt.write_str(&serde_json::to_string_pretty(self).expect("unable to serialize json"))
fmt.write_str(&serde_json::to_string_pretty(self).unwrap())
}
fn write_rs(&self, fmt: &mut Formatter<'_>) -> fmt::Result {

View File

@@ -31,7 +31,7 @@ impl CodeWriter for SchemaMap {
};
writeln!(fmt, "// Alignment: {}", enum_.alignment)?;
writeln!(fmt, "// Members count: {}", enum_.size)?;
writeln!(fmt, "// Member count: {}", enum_.size)?;
fmt.block(
&format!("public enum {} : {}", slugify(&enum_.name), type_name),
@@ -59,7 +59,7 @@ impl CodeWriter for SchemaMap {
.unwrap_or_else(|| String::from("None"));
writeln!(fmt, "// Parent: {}", parent_name)?;
writeln!(fmt, "// Fields count: {}", class.fields.len())?;
writeln!(fmt, "// Field count: {}", class.fields.len())?;
write_metadata(fmt, &class.metadata)?;
@@ -114,7 +114,7 @@ impl CodeWriter for SchemaMap {
};
writeln!(fmt, "// Alignment: {}", enum_.alignment)?;
writeln!(fmt, "// Members count: {}", enum_.size)?;
writeln!(fmt, "// Member count: {}", enum_.size)?;
fmt.block(
&format!("enum class {} : {}", slugify(&enum_.name), type_name),
@@ -142,7 +142,7 @@ impl CodeWriter for SchemaMap {
.unwrap_or_else(|| String::from("None"));
writeln!(fmt, "// Parent: {}", parent_name)?;
writeln!(fmt, "// Fields count: {}", class.fields.len())?;
writeln!(fmt, "// Field count: {}", class.fields.len())?;
write_metadata(fmt, &class.metadata)?;
@@ -255,7 +255,7 @@ impl CodeWriter for SchemaMap {
})
.collect();
fmt.write_str(&serde_json::to_string_pretty(&content).expect("unable to serialize json"))
fmt.write_str(&serde_json::to_string_pretty(&content).unwrap())
}
fn write_rs(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
@@ -285,7 +285,7 @@ impl CodeWriter for SchemaMap {
};
writeln!(fmt, "// Alignment: {}", enum_.alignment)?;
writeln!(fmt, "// Members count: {}", enum_.size)?;
writeln!(fmt, "// Member count: {}", enum_.size)?;
fmt.block(
&format!(
@@ -330,7 +330,7 @@ impl CodeWriter for SchemaMap {
.unwrap_or_else(|| String::from("None"));
writeln!(fmt, "// Parent: {}", parent_name)?;
writeln!(fmt, "// Fields count: {}", class.fields.len())?;
writeln!(fmt, "// Field count: {}", class.fields.len())?;
write_metadata(fmt, &class.metadata)?;

View File

@@ -15,8 +15,8 @@ pub struct SchemaClassInfoData {
pub name: Pointer64<ReprCString>, // 0x0008
pub module_name: Pointer64<ReprCString>, // 0x0010
pub size: i32, // 0x0018
pub fields_count: i16, // 0x001C
pub static_fields_count: i16, // 0x001E
pub field_count: i16, // 0x001C
pub static_field_count: i16, // 0x001E
pub static_metadata_count: i16, // 0x0020
pub align_of: u8, // 0x0022
pub has_base_class: u8, // 0x0023

View File

@@ -14,7 +14,7 @@ pub struct SchemaEnumInfoData {
pub size: u8, // 0x0018
pub align_of: u8, // 0x0019
pad_001a: [u8; 0x2], // 0x001A
pub enumerators_count: u16, // 0x001C
pub enumerator_count: u16, // 0x001C
pub static_metadata_count: u16, // 0x001E
pub enumerators: Pointer64<[SchemaEnumeratorInfoData]>, // 0x0020
pub static_metadata: Pointer64<SchemaMetadataEntryData>, // 0x0028

View File

@@ -1,6 +1,6 @@
use memflow::prelude::v1::*;
use anyhow::{bail, Result};
use crate::error::{Error, Result};
use memflow::prelude::v1::*;
#[repr(C)]
pub struct UtlMemory<T> {
@@ -16,11 +16,14 @@ impl<T: Pod> UtlMemory<T> {
}
pub fn element(&self, process: &mut IntoProcessInstanceArcBox<'_>, idx: usize) -> Result<T> {
if idx >= self.count() as usize {
return Err(Error::Other("index out of bounds"));
if idx >= self.count() as _ {
bail!("index out of bounds");
}
self.mem.at(idx as _).read(process).map_err(Into::into)
process
.read_ptr(self.mem.at(idx as _))
.data_part()
.map_err(Into::into)
}
#[inline]

View File

@@ -1,8 +1,9 @@
use anyhow::Result;
use memflow::prelude::v1::*;
use super::UtlMemoryPoolBase;
use crate::error::Result;
use crate::mem::PointerExt;
#[repr(C)]
@@ -70,13 +71,13 @@ where
let mut cur_element = bucket.first_uncommitted;
while !cur_element.is_null() {
let element = cur_element.read(process)?;
let element = process.read_ptr(cur_element).data_part()?;
if !element.data.is_null() {
unallocated_list.push(element.data);
allocated_list.push(element.data);
}
if unallocated_list.len() >= blocks_alloc {
if allocated_list.len() >= blocks_alloc {
break;
}
@@ -88,13 +89,13 @@ where
Pointer64::<HashAllocatedBlob<D>>::from(self.entry_mem.free_list_head.address());
while !cur_blob.is_null() {
let blob = cur_blob.read(process)?;
let blob = process.read_ptr(cur_blob).data_part()?;
if !blob.data.is_null() {
allocated_list.push(blob.data);
unallocated_list.push(blob.data);
}
if allocated_list.len() >= peak_alloc {
if unallocated_list.len() >= peak_alloc {
break;
}

View File

@@ -1,6 +1,6 @@
use memflow::prelude::v1::*;
use anyhow::{bail, Result};
use crate::error::{Error, Result};
use memflow::prelude::v1::*;
#[repr(C)]
pub struct UtlVector<T> {
@@ -17,10 +17,13 @@ impl<T: Pod> UtlVector<T> {
pub fn element(&self, process: &mut IntoProcessInstanceArcBox<'_>, idx: usize) -> Result<T> {
if idx >= self.count() as usize {
return Err(Error::Other("index out of bounds"));
bail!("index out of bounds");
}
self.mem.at(idx as _).read(process).map_err(Into::into)
process
.read_ptr(self.mem.at(idx as _))
.data_part()
.map_err(Into::into)
}
}