Merge dev branch into main

This commit is contained in:
a2x
2024-03-28 22:19:20 +10:00
parent 755093fe06
commit 889ef7dcd8
315 changed files with 552043 additions and 333811 deletions

76
src/analysis/buttons.rs Normal file
View File

@@ -0,0 +1,76 @@
use std::env;
use log::debug;
use memflow::prelude::v1::*;
use serde::{Deserialize, Serialize};
use skidscan_macros::signature;
use crate::error::{Error, Result};
use crate::source_engine::KeyboardKey;
#[derive(Debug, Deserialize, Serialize)]
pub struct Button {
pub name: String,
pub value: u32,
}
pub fn buttons(process: &mut IntoProcessInstanceArcBox<'_>) -> Result<Vec<Button>> {
let (module_name, sig) = match env::consts::OS {
"linux" => (
"libclient.so",
signature!("48 8B 15 ? ? ? ? 48 89 83 ? ? ? ? 48 85 D2"),
),
"windows" => (
"client.dll",
signature!("48 8B 15 ? ? ? ? 48 85 D2 74 ? 0F 1F 40"),
),
_ => panic!("unsupported os"),
};
let module = process.module_by_name(&module_name)?;
let buf = process.read_raw(module.base, module.size as _)?;
let list_addr = sig
.scan(&buf)
.and_then(|ptr| process.read_addr64_rip(module.base + ptr).ok())
.ok_or_else(|| Error::Other("unable to read button list address"))?;
read_buttons(process, &module, list_addr)
}
fn read_buttons(
process: &mut IntoProcessInstanceArcBox<'_>,
module: &ModuleInfo,
list_addr: Address,
) -> Result<Vec<Button>> {
let mut buttons = Vec::new();
let mut key_ptr = Pointer64::<KeyboardKey>::from(process.read_addr64(list_addr)?);
while !key_ptr.is_null() {
let key = process.read_ptr(key_ptr)?;
let name = process.read_char_string(key.name.address())?;
let value =
((key_ptr.address() - module.base) + offset_of!(KeyboardKey.state) as i64) as u32;
debug!(
"found button: {} at {:#X} ({} + {:#X})",
name,
value as u64 + module.base.to_umem(),
module.name,
value
);
buttons.push(Button { name, value });
key_ptr = key.next;
}
buttons.sort_unstable_by(|a, b| a.name.cmp(&b.name));
Ok(buttons)
}

View File

@@ -0,0 +1,80 @@
use std::collections::BTreeMap;
use std::env;
use log::debug;
use memflow::prelude::v1::*;
use serde::{Deserialize, Serialize};
use skidscan_macros::signature;
use crate::error::Result;
use crate::source_engine::InterfaceReg;
pub type InterfaceMap = BTreeMap<String, Vec<Interface>>;
#[derive(Debug, Deserialize, Serialize)]
pub struct Interface {
pub name: String,
pub value: u32,
}
pub fn interfaces(process: &mut IntoProcessInstanceArcBox<'_>) -> Result<InterfaceMap> {
let sig = match env::consts::OS {
"linux" => signature!("48 8B 1D ? ? ? ? 48 85 DB 74 ? 49 89 FC"),
"windows" => signature!("4C 8B 0D ? ? ? ? 4C 8B D2 4C 8B D9"),
_ => panic!("unsupported os"),
};
process
.module_list()?
.iter()
.filter(|module| !module.name.starts_with("nvidia")) // Temporary workaround for upstream bug: https://github.com/memflow/memflow-native/blob/1b063fc573957498b88a13b6120120480bc65ea5/src/linux/mem.rs#L168
.filter_map(|module| {
let buf = process.read_raw(module.base, module.size as _).ok()?;
let list_addr = sig
.scan(&buf)
.and_then(|ptr| process.read_addr64_rip(module.base + ptr).ok())?;
read_interfaces(process, module, list_addr)
.ok()
.filter(|ifaces| !ifaces.is_empty())
.map(|ifaces| Ok((module.name.to_string(), ifaces)))
})
.collect()
}
fn read_interfaces(
process: &mut IntoProcessInstanceArcBox<'_>,
module: &ModuleInfo,
list_addr: Address,
) -> Result<Vec<Interface>> {
let mut ifaces = Vec::new();
let mut reg_ptr = Pointer64::<InterfaceReg>::from(process.read_addr64(list_addr)?);
while !reg_ptr.is_null() {
let reg = process.read_ptr(reg_ptr)?;
let name = process.read_char_string(reg.name.address())?;
let value = (reg.create_fn - module.base) as u32;
debug!(
"found interface: {} at {:#X} ({} + {:#X})",
name,
value as u64 + module.base.to_umem(),
module.name,
value
);
ifaces.push(Interface { name, value });
reg_ptr = reg.next;
}
ifaces.sort_unstable_by(|a, b| a.name.cmp(&b.name));
Ok(ifaces)
}

9
src/analysis/mod.rs Normal file
View File

@@ -0,0 +1,9 @@
pub use buttons::*;
pub use interfaces::*;
pub use offsets::*;
pub use schemas::*;
pub mod buttons;
pub mod interfaces;
pub mod offsets;
pub mod schemas;

95
src/analysis/offsets.rs Normal file
View File

@@ -0,0 +1,95 @@
use std::collections::BTreeMap;
use std::mem;
use std::str::FromStr;
use log::{debug, error};
use memflow::prelude::v1::*;
use serde::{Deserialize, Serialize};
use crate::config::{Operation, Signature, CONFIG};
use crate::error::{Error, Result};
pub type OffsetMap = BTreeMap<String, Vec<Offset>>;
#[derive(Debug, Deserialize, Serialize)]
pub struct Offset {
pub name: String,
pub value: u32,
}
pub fn offsets(process: &mut IntoProcessInstanceArcBox<'_>) -> Result<OffsetMap> {
let mut map = BTreeMap::new();
for (module_name, sigs) in CONFIG.signatures.iter().flatten() {
let module = process.module_by_name(module_name)?;
let mut offsets: Vec<_> = sigs
.iter()
.filter_map(|sig| match read_offset(process, &module, sig) {
Ok(offset) => Some(offset),
Err(err) => {
error!("{}", err);
None
}
})
.collect();
if !offsets.is_empty() {
offsets.sort_unstable_by(|a, b| a.name.cmp(&b.name));
map.insert(module_name.to_string(), offsets);
}
}
Ok(map)
}
fn read_offset(
process: &mut IntoProcessInstanceArcBox<'_>,
module: &ModuleInfo,
signature: &Signature,
) -> Result<Offset> {
let buf = process.read_raw(module.base, module.size as _)?;
let addr = skidscan::Signature::from_str(&signature.pattern)?
.scan(&buf)
.ok_or_else(|| Error::SignatureNotFound(signature.name.clone()))?;
let mut result = module.base + addr;
for op in &signature.operations {
result = match op {
Operation::Add { value } => result + *value,
Operation::Rip { offset, len } => {
let offset: i32 = process.read(result + offset.unwrap_or(3))?;
(result + offset) + len.unwrap_or(7)
}
Operation::Read => process.read_addr64(result)?,
Operation::Slice { start, end } => {
let buf = process.read_raw(result + *start, end - start)?;
let mut bytes = [0; mem::size_of::<usize>()];
bytes[..buf.len()].copy_from_slice(&buf);
usize::from_le_bytes(bytes).into()
}
Operation::Sub { value } => result - *value,
};
}
let value = (result - module.base)
.try_into()
.map_or_else(|_| result.to_umem() as u32, |v| v);
debug!("found offset: {} at {:#X}", signature.name, value);
Ok(Offset {
name: signature.name.clone(),
value,
})
}

337
src/analysis/schemas.rs Normal file
View File

@@ -0,0 +1,337 @@
use std::collections::BTreeMap;
use std::ffi::CStr;
use std::ops::Add;
use std::{env, mem};
use log::debug;
use memflow::prelude::v1::*;
use serde::{Deserialize, Serialize};
use skidscan_macros::signature;
use crate::error::{Error, Result};
use crate::source_engine::*;
pub type SchemaMap = BTreeMap<String, (Vec<Class>, Vec<Enum>)>;
#[derive(Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
pub enum ClassMetadata {
Unknown { name: String },
NetworkChangeCallback { name: String },
NetworkVarNames { name: String, ty: String },
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Class {
pub name: String,
pub module_name: String,
pub parent: Option<Box<Class>>,
pub metadata: Vec<ClassMetadata>,
pub fields: Vec<ClassField>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct ClassField {
pub name: String,
pub ty: String,
pub offset: u32,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Enum {
pub name: String,
pub ty: String,
pub alignment: u8,
pub size: u16,
pub members: Vec<EnumMember>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct EnumMember {
pub name: String,
pub value: i64,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct TypeScope {
pub name: String,
pub classes: Vec<Class>,
pub enums: Vec<Enum>,
}
pub fn schemas(process: &mut IntoProcessInstanceArcBox<'_>) -> Result<SchemaMap> {
let schema_system = read_schema_system(process)?;
let type_scopes = read_type_scopes(process, &schema_system)?;
let map: BTreeMap<_, _> = type_scopes
.into_iter()
.map(|type_scope| (type_scope.name, (type_scope.classes, type_scope.enums)))
.collect();
Ok(map)
}
fn read_class_binding(
process: &mut IntoProcessInstanceArcBox<'_>,
binding_ptr: Pointer64<SchemaClassBinding>,
) -> Result<Class> {
let binding = process.read_ptr(binding_ptr)?;
let module_name = process
.read_char_string(binding.module_name.address())
.map(|s| {
format!(
"{}.{}",
s,
match env::consts::OS {
"linux" => "so",
"windows" => ".dll",
_ => panic!("unsupported os"),
}
)
})?;
let name = process.read_char_string(binding.name.address())?;
let parent = binding.base_classes.non_null().and_then(|ptr| {
let base_class = process.read_ptr(ptr).ok()?;
read_class_binding(process, base_class.prev)
.ok()
.map(Box::new)
});
let metadata = read_class_binding_metadata(process, &binding)?;
let fields = read_class_binding_fields(process, &binding)?;
debug!(
"found class: {} at {:#X} (module name: {}) (parent name: {:?}) (metadata count: {}) (fields count: {})",
name,
binding_ptr.to_umem(),
module_name,
parent.as_ref().map(|parent| parent.name.clone()),
metadata.len(),
fields.len()
);
Ok(Class {
name,
module_name,
parent,
metadata,
fields,
})
}
fn read_class_binding_fields(
process: &mut IntoProcessInstanceArcBox<'_>,
binding: &SchemaClassBinding,
) -> Result<Vec<ClassField>> {
(0..binding.fields_count)
.map(|i| {
let field_ptr: Pointer64<SchemaClassFieldData> = binding
.fields
.address()
.add(i * mem::size_of::<SchemaClassFieldData>() as u16)
.into();
let field = process.read_ptr(field_ptr)?;
if field.schema_type.is_null() {
return Err(Error::Other("field schema type is null"));
}
let name = process.read_char_string(field.name.address())?;
let schema_type = process.read_ptr(field.schema_type)?;
// TODO: Parse this properly.
let ty = process.read_char_string(schema_type.name.address())?;
Ok(ClassField {
name,
ty,
offset: field.offset,
})
})
.collect()
}
fn read_class_binding_metadata(
process: &mut IntoProcessInstanceArcBox<'_>,
binding: &SchemaClassBinding,
) -> Result<Vec<ClassMetadata>> {
if binding.static_metadata.is_null() {
return Ok(Vec::new());
}
(0..binding.static_metadata_count)
.map(|i| {
let metadata_ptr: Pointer64<SchemaMetadataEntryData> =
binding.static_metadata.offset(i as _).into();
let metadata = process.read_ptr(metadata_ptr)?;
if metadata.network_value.is_null() {
return Err(Error::Other("class metadata network value is null"));
}
let name = process.read_char_string(metadata.name.address())?;
let network_value = process.read_ptr(metadata.network_value)?;
let metadata = match name.as_str() {
"MNetworkChangeCallback" => unsafe {
let name =
process.read_char_string(network_value.union_data.name_ptr.address())?;
ClassMetadata::NetworkChangeCallback { name }
},
"MNetworkVarNames" => unsafe {
let var_value = network_value.union_data.var_value;
let name = process.read_char_string(var_value.name.address())?;
let ty = process.read_char_string(var_value.ty.address())?;
ClassMetadata::NetworkVarNames { name, ty }
},
_ => ClassMetadata::Unknown { name },
};
Ok(metadata)
})
.collect()
}
fn read_enum_binding(
process: &mut IntoProcessInstanceArcBox<'_>,
binding_ptr: Pointer64<SchemaEnumBinding>,
) -> Result<Enum> {
let binding = process.read_ptr(binding_ptr)?;
let name = process.read_char_string(binding.name.address())?;
let members = read_enum_binding_members(process, &binding)?;
debug!(
"found enum: {} at {:#X} (type name: {}) (alignment: {}) (members count: {})",
name,
binding_ptr.to_umem(),
binding.type_name(),
binding.alignment,
binding.size,
);
Ok(Enum {
name,
ty: binding.type_name().to_string(),
alignment: binding.alignment,
size: binding.size,
members,
})
}
fn read_enum_binding_members(
process: &mut IntoProcessInstanceArcBox<'_>,
binding: &SchemaEnumBinding,
) -> Result<Vec<EnumMember>> {
(0..binding.size)
.map(|i| {
let enumerator_info_ptr: Pointer64<SchemaEnumeratorInfoData> = binding
.enum_info
.address()
.add(i * mem::size_of::<SchemaEnumeratorInfoData>() as u16)
.into();
let enumerator_info = process.read_ptr(enumerator_info_ptr)?;
let name = process.read_char_string(enumerator_info.name.address())?;
let value = {
let value = unsafe { enumerator_info.union_data.ulong } as i64;
if value == i64::MAX {
-1
} else {
value
}
};
Ok(EnumMember { name, value })
})
.collect()
}
fn read_schema_system(process: &mut IntoProcessInstanceArcBox<'_>) -> Result<SchemaSystem> {
let (module_name, sig) = match env::consts::OS {
"linux" => (
"libschemasystem.so",
signature!("48 8D 35 ? ? ? ? 48 8D 3D ? ? ? ? E8 ? ? ? ? 48 8D 15 ? ? ? ? 48 8D 35 ? ? ? ? 48 8D 3D"),
),
"windows" => (
"schemasystem.dll",
signature!("48 89 05 ? ? ? ? 4C 8D 45"),
),
_ => panic!("unsupported os"),
};
let module = process.module_by_name(&module_name)?;
let buf = process.read_raw(module.base, module.size as _)?;
let addr = sig
.scan(&buf)
.and_then(|ptr| process.read_addr64_rip(module.base + ptr).ok())
.ok_or_else(|| Error::Other("unable to read schema system address"))?;
let schema_system: SchemaSystem = process.read(addr)?;
if schema_system.num_registrations == 0 {
return Err(Error::Other("no schema system registrations found"));
}
Ok(schema_system)
}
fn read_type_scopes(
process: &mut IntoProcessInstanceArcBox<'_>,
schema_system: &SchemaSystem,
) -> Result<Vec<TypeScope>> {
let type_scopes = &schema_system.type_scopes;
(0..type_scopes.size)
.map(|i| {
let type_scope_ptr = type_scopes.get(process, i as _)?;
let type_scope = process.read_ptr(type_scope_ptr)?;
let name = unsafe { CStr::from_ptr(type_scope.name.as_ptr()) }
.to_string_lossy()
.to_string();
let classes: Vec<_> = type_scope
.class_bindings
.elements(process)?
.iter()
.filter_map(|ptr| read_class_binding(process, *ptr).ok())
.collect();
let enums: Vec<_> = type_scope
.enum_bindings
.elements(process)?
.iter()
.filter_map(|ptr| read_enum_binding(process, *ptr).ok())
.collect();
debug!(
"found type scope: {} at {:#X} (classes count: {}) (enums count: {})",
name,
type_scope_ptr.to_umem(),
classes.len(),
enums.len()
);
Ok(TypeScope {
name,
classes,
enums,
})
})
.collect()
}