* Bug fixes
This commit is contained in:
a2x
2026-01-26 07:18:17 +10:00
parent a03f9725e9
commit e709e1e524
110 changed files with 80569 additions and 1020 deletions

View File

@@ -11,7 +11,7 @@ use pelite::pe64::{Pe, PeView};
use crate::source2::KeyButton;
pub type ButtonMap = BTreeMap<String, imem>;
pub type ButtonMap = BTreeMap<String, umem>;
pub fn buttons<P: Process + MemoryView>(process: &mut P) -> Result<ButtonMap> {
let module = process.module_by_name("client.dll")?;
@@ -31,35 +31,40 @@ pub fn buttons<P: Process + MemoryView>(process: &mut P) -> Result<ButtonMap> {
bail!("outdated button list pattern");
}
read_buttons(process, &module, module.base + save[1])
let list_head = process.read_addr64(module.base + save[1]).data_part()?;
read_buttons(process, &module, list_head)
}
fn read_buttons(
mem: &mut impl MemoryView,
module: &ModuleInfo,
list_addr: Address,
list_head: Address,
) -> Result<ButtonMap> {
let mut map = ButtonMap::new();
let mut result = ButtonMap::new();
let mut cur_button = Pointer64::<KeyButton>::from(mem.read_addr64(list_addr).data_part()?);
let mut button_ptr = Pointer64::<KeyButton>::from(list_head);
while !cur_button.is_null() {
let button = mem.read_ptr(cur_button).data_part()?;
let name = mem.read_utf8(button.name.address(), 32).data_part()?;
let rva = (cur_button.address() - module.base) + offset_of!(KeyButton.state) as imem;
while !button_ptr.is_null() {
let button = mem.read_ptr(button_ptr).data_part()?;
let name = mem.read_utf8_lossy(button.name.address(), 32).data_part()?;
debug!(
"found button: {} at {:#X} ({} + {:#X})",
name,
cur_button.to_umem() + offset_of!(KeyButton.state) as umem,
module.name,
rva
);
let state_addr = button_ptr.address() + offset_of!(KeyButton.state);
map.insert(name, rva);
if let Some(state_rva) = state_addr.to_umem().checked_sub(module.base.to_umem()) {
debug!(
"found \"{}\" at {:#X} ({} + {:#X})",
name,
state_addr.to_umem(),
module.name,
state_rva
);
cur_button = button.next;
result.insert(name, state_rva);
}
button_ptr = button.next;
}
Ok(map)
Ok(result)
}

View File

@@ -9,6 +9,7 @@ use memflow::prelude::v1::*;
use pelite::pe64::exports::Export;
use pelite::pe64::{Pe, PeView};
use crate::memory::address;
use crate::source2::InterfaceReg;
pub type InterfaceMap = BTreeMap<String, BTreeMap<String, umem>>;
@@ -17,7 +18,6 @@ pub fn interfaces<P: Process + MemoryView>(process: &mut P) -> Result<InterfaceM
process
.module_list()?
.iter()
.filter(|module| module.name.as_ref() != "crashandler64.dll")
.filter_map(|module| {
let buf = process
.read_raw(module.base, module.size as _)
@@ -34,16 +34,18 @@ pub fn interfaces<P: Process + MemoryView>(process: &mut P) -> Result<InterfaceM
.name("CreateInterface")
.ok()?;
if let Export::Symbol(symbol) = ci_export {
let list_addr = read_addr64_rip(process, module.base + symbol).ok()?;
match ci_export {
Export::Symbol(symbol) => {
let list_ptr = address::resolve_rip(process, module.base + symbol).ok()?;
let list_head = process.read_addr64(list_ptr).data_part().ok()?;
return read_interfaces(process, module, list_addr)
.ok()
.filter(|ifaces| !ifaces.is_empty())
.map(|ifaces| Ok((module.name.to_string(), ifaces)));
return read_interfaces(process, module, list_head)
.ok()
.filter(|ifaces| !ifaces.is_empty())
.map(|ifaces| Ok((module.name.to_string(), ifaces)));
}
_ => None,
}
None
})
.collect()
}
@@ -51,36 +53,32 @@ pub fn interfaces<P: Process + MemoryView>(process: &mut P) -> Result<InterfaceM
fn read_interfaces(
mem: &mut impl MemoryView,
module: &ModuleInfo,
list_addr: Address,
list_head: Address,
) -> Result<BTreeMap<String, umem>> {
let mut ifaces = BTreeMap::new();
let mut result = BTreeMap::new();
let mut cur_reg = Pointer64::<InterfaceReg>::from(mem.read_addr64(list_addr).data_part()?);
let mut reg_ptr = Pointer64::<InterfaceReg>::from(list_head);
while !cur_reg.is_null() {
let reg = mem.read_ptr(cur_reg).data_part()?;
let name = mem.read_utf8(reg.name.address(), 128).data_part()?;
let instance = read_addr64_rip(mem, reg.create_fn.address())?;
let value = instance.wrapping_sub(module.base).to_umem();
while !reg_ptr.is_null() {
let reg = mem.read_ptr(reg_ptr).data_part()?;
let name = mem.read_utf8_lossy(reg.name.address(), 128).data_part()?;
debug!(
"found interface: {} at {:#X} ({} + {:#X})",
name,
value.wrapping_add(module.base.to_umem()),
module.name,
value
);
let instance_addr = address::resolve_rip(mem, reg.create_fn.address())?;
ifaces.insert(name, value);
if let Some(instance_rva) = instance_addr.to_umem().checked_sub(module.base.to_umem()) {
debug!(
"found \"{}\" at {:#X} ({} + {:#X})",
name,
instance_addr.to_umem(),
module.name,
instance_rva
);
cur_reg = reg.next;
result.insert(name, instance_rva);
}
reg_ptr = reg.next;
}
Ok(ifaces)
}
fn read_addr64_rip(mem: &mut impl MemoryView, addr: Address) -> Result<Address> {
let disp = mem.read::<i32>(addr + 0x3).data_part()?;
Ok(addr + 0x7 + disp)
Ok(result)
}

View File

@@ -55,7 +55,7 @@ macro_rules! pattern_map {
for (name, value) in &map {
debug!(
"found offset: {} at {:#X} ({}.dll + {:#X})",
"found \"{}\" at {:#X} ({}.dll + {:#X})",
name,
*value as u64 + view.optional_header().ImageBase,
stringify!($module),
@@ -154,12 +154,145 @@ pub fn offsets<P: Process + MemoryView>(process: &mut P) -> Result<OffsetMap> {
#[cfg(test)]
mod tests {
use std::fs;
use std::sync::Once;
use serde_json::Value;
use simplelog::*;
use super::*;
#[test]
fn build_number() -> Result<()> {
let mut process = setup()?;
let engine_base = process.module_by_name("engine2.dll")?.base;
let offset = read_offset("engine2.dll", "dwBuildNumber").unwrap();
let build_number: u32 = process.read(engine_base + offset).data_part()?;
debug!("build number: {}", build_number);
Ok(())
}
#[test]
fn global_vars() -> Result<()> {
let mut process = setup()?;
let client_base = process.module_by_name("client.dll")?.base;
let offset = read_offset("client.dll", "dwGlobalVars").unwrap();
let global_vars: u64 = process.read(client_base + offset).data_part()?;
let map_name_addr = process
.read_addr64((global_vars + 0x180).into())
.data_part()?;
let map_name = process.read_utf8(map_name_addr, 128).data_part()?;
debug!("[global vars] map name: \"{}\"", map_name);
Ok(())
}
#[test]
fn local_controller() -> Result<()> {
let mut process = setup()?;
let client_base = process.module_by_name("client.dll")?.base;
let local_controller_offset = read_offset("client.dll", "dwLocalPlayerController").unwrap();
let player_name_offset =
read_class_field("client.dll", "CBasePlayerController", "m_iszPlayerName").unwrap();
let local_controller: u64 = process
.read(client_base + local_controller_offset)
.data_part()?;
let player_name = process
.read_utf8((local_controller + player_name_offset).into(), 128)
.data_part()?;
debug!("[local controller] name: \"{}\"", player_name);
Ok(())
}
#[test]
fn local_pawn() -> Result<()> {
#[derive(Pod)]
#[repr(C)]
struct Vector3D {
x: f32,
y: f32,
z: f32,
}
let mut process = setup()?;
let client_base = process.module_by_name("client.dll")?.base;
let local_player_pawn_offset = read_offset("client.dll", "dwLocalPlayerPawn").unwrap();
let game_scene_node_offset =
read_class_field("client.dll", "C_BaseEntity", "m_pGameSceneNode").unwrap();
let origin_offset =
read_class_field("client.dll", "CGameSceneNode", "m_vecAbsOrigin").unwrap();
let local_player_pawn: u64 = process
.read(client_base + local_player_pawn_offset)
.data_part()?;
let game_scene_node: u64 = process
.read((local_player_pawn + game_scene_node_offset).into())
.data_part()?;
let origin: Vector3D = process
.read((game_scene_node + origin_offset).into())
.data_part()?;
debug!(
"[local pawn] origin: {:.2}, y: {:.2}, z: {:.2}",
origin.x, origin.y, origin.z
);
Ok(())
}
#[test]
fn window_size() -> Result<()> {
let mut process = setup()?;
let engine_base = process.module_by_name("engine2.dll")?.base;
let window_width_offset = read_offset("engine2.dll", "dwWindowWidth").unwrap();
let window_height_offset = read_offset("engine2.dll", "dwWindowHeight").unwrap();
let window_width: u32 = process
.read(engine_base + window_width_offset)
.data_part()?;
let window_height: u32 = process
.read(engine_base + window_height_offset)
.data_part()?;
debug!("window size: {}x{}", window_width, window_height);
Ok(())
}
fn setup() -> Result<IntoProcessInstanceArcBox<'static>> {
static LOGGER: Once = Once::new();
LOGGER.call_once(|| {
SimpleLogger::init(LevelFilter::Trace, Config::default()).ok();
});
let os = memflow_native::create_os(&OsArgs::default(), LibArc::default())?;
let process = os.into_process_by_name("cs2.exe")?;
@@ -167,7 +300,7 @@ mod tests {
Ok(process)
}
fn get_class_field_value(module_name: &str, class_name: &str, field_name: &str) -> Option<u64> {
fn read_class_field(module_name: &str, class_name: &str, field_name: &str) -> Option<u64> {
let content =
fs::read_to_string(format!("output/{}.json", module_name.replace(".", "_"))).ok()?;
@@ -182,7 +315,7 @@ mod tests {
.as_u64()
}
fn get_offset_value(module_name: &str, offset_name: &str) -> Option<u64> {
fn read_offset(module_name: &str, offset_name: &str) -> Option<u64> {
let content = fs::read_to_string("output/offsets.json").ok()?;
let value: Value = serde_json::from_str(&content).ok()?;
@@ -190,129 +323,4 @@ mod tests {
offset.as_u64()
}
#[test]
fn build_number() -> Result<()> {
let mut process = setup()?;
let engine_base = process.module_by_name("engine2.dll")?.base;
let offset = get_offset_value("engine2.dll", "dwBuildNumber").unwrap();
let build_number: u32 = process.read(engine_base + offset).data_part()?;
println!("build number: {}", build_number);
Ok(())
}
#[test]
fn global_vars() -> Result<()> {
let mut process = setup()?;
let client_base = process.module_by_name("client.dll")?.base;
let offset = get_offset_value("client.dll", "dwGlobalVars").unwrap();
let global_vars: u64 = process.read(client_base + offset).data_part()?;
let cur_map_name = {
let addr = process
.read_addr64((global_vars + 0x180).into())
.data_part()?;
process.read_utf8(addr, 128).data_part()?
};
println!("current map name: {}", cur_map_name);
Ok(())
}
#[test]
fn local_player_controller() -> Result<()> {
let mut process = setup()?;
let client_base = process.module_by_name("client.dll")?.base;
let local_player_controller_offset =
get_offset_value("client.dll", "dwLocalPlayerController").unwrap();
let player_name_offset =
get_class_field_value("client.dll", "CBasePlayerController", "m_iszPlayerName")
.unwrap();
let local_player_controller: u64 = process
.read(client_base + local_player_controller_offset)
.data_part()?;
let player_name = process
.read_utf8((local_player_controller + player_name_offset).into(), 4096)
.data_part()?;
println!("local player name: {}", player_name);
Ok(())
}
#[test]
fn local_player_pawn() -> Result<()> {
#[derive(Debug, Pod)]
#[repr(C)]
struct Vector3D {
x: f32,
y: f32,
z: f32,
}
let mut process = setup()?;
let client_base = process.module_by_name("client.dll")?.base;
let local_player_pawn_offset = get_offset_value("client.dll", "dwLocalPlayerPawn").unwrap();
let game_scene_node_offset =
get_class_field_value("client.dll", "C_BaseEntity", "m_pGameSceneNode").unwrap();
let vec_abs_origin_offset =
get_class_field_value("client.dll", "CGameSceneNode", "m_vecAbsOrigin").unwrap();
let local_player_pawn: u64 = process
.read(client_base + local_player_pawn_offset)
.data_part()?;
let game_scene_node: u64 = process
.read((local_player_pawn + game_scene_node_offset).into())
.data_part()?;
let vec_abs_origin: Vector3D = process
.read((game_scene_node + vec_abs_origin_offset).into())
.data_part()?;
println!("local player origin: {:?}", vec_abs_origin);
Ok(())
}
#[test]
fn window_size() -> Result<()> {
let mut process = setup()?;
let engine_base = process.module_by_name("engine2.dll")?.base;
let window_width_offset = get_offset_value("engine2.dll", "dwWindowWidth").unwrap();
let window_height_offset = get_offset_value("engine2.dll", "dwWindowHeight").unwrap();
let window_width: u32 = process
.read(engine_base + window_width_offset)
.data_part()?;
let window_height: u32 = process
.read(engine_base + window_height_offset)
.data_part()?;
println!("window size: {}x{}", window_width, window_height);
Ok(())
}
}

View File

@@ -27,7 +27,7 @@ pub enum ClassMetadata {
pub struct Class {
pub name: String,
pub module_name: String,
pub parent: Option<Box<Class>>,
pub parent_name: Option<String>,
pub metadata: Vec<ClassMetadata>,
pub fields: Vec<ClassField>,
}
@@ -64,7 +64,7 @@ pub fn schemas<P: Process + MemoryView>(process: &mut P) -> Result<SchemaMap> {
let schema_system = read_schema_system(process)?;
let type_scopes = read_type_scopes(process, &schema_system)?;
let map = type_scopes
Ok(type_scopes
.into_iter()
.map(|type_scope| {
(
@@ -72,9 +72,7 @@ pub fn schemas<P: Process + MemoryView>(process: &mut P) -> Result<SchemaMap> {
(type_scope.classes, type_scope.enums),
)
})
.collect();
Ok(map)
.collect())
}
fn read_class_binding(
@@ -86,51 +84,35 @@ fn read_class_binding(
let module_name = mem
.read_utf8_lossy(binding.module_name.address(), 128)
.data_part()
.map(|s| format!("{}.dll", s))?;
.map(|m| format!("{}.dll", m))?;
let name = mem
.read_utf8_lossy(binding.name.address(), 4096)
.read_utf8_lossy(binding.name.address(), 128)
.data_part()?;
if name.is_empty() {
bail!("empty class name");
bail!("invalid class name");
}
let parent = binding.base_classes.non_null().and_then(|ptr| {
let parent_name = binding.base_classes.non_null().and_then(|ptr| {
let base_class = mem.read_ptr(ptr).data_part().ok()?;
let parent_class = mem.read_ptr(base_class.prev).data_part().ok()?;
let parent_class = mem.read_ptr(base_class.class).data_part().ok()?;
let name = mem
.read_utf8_lossy(parent_class.name.address(), 4096)
let parent_name = mem
.read_utf8_lossy(parent_class.name.address(), 128)
.data_part()
.ok()?;
Some(Box::new(Class {
name,
module_name: String::new(),
parent: None,
metadata: Vec::new(),
fields: Vec::new(),
}))
(!parent_name.is_empty()).then_some(parent_name)
});
let fields = read_class_binding_fields(mem, &binding)?;
let metadata = read_class_binding_metadata(mem, &binding)?;
debug!(
"found class: {} at {:#X} (module name: {}) (parent name: {:?}) (metadata count: {}) (field count: {})",
name,
binding_ptr.to_umem(),
module_name,
parent.as_ref().map(|p| p.name.clone()),
metadata.len(),
fields.len(),
);
Ok(Class {
name,
module_name,
parent,
parent_name,
metadata,
fields,
})
@@ -151,13 +133,9 @@ fn read_class_binding_fields(
return Ok(acc);
}
let name = mem
.read_utf8_lossy(field.name.address(), 4096)
.data_part()?;
let name = mem.read_utf8_lossy(field.name.address(), 128).data_part()?;
let r#type = mem.read_ptr(field.r#type).data_part()?;
// TODO: Parse this properly.
let type_name = mem
.read_utf8_lossy(r#type.name.address(), 128)
.data_part()?
@@ -191,7 +169,7 @@ fn read_class_binding_metadata(
}
let name = mem
.read_utf8_lossy(metadata.name.address(), 4096)
.read_utf8_lossy(metadata.name.address(), 128)
.data_part()?;
let network_value = mem.read_ptr(metadata.network_value).data_part()?;
@@ -199,7 +177,7 @@ fn read_class_binding_metadata(
let metadata = match name.as_str() {
"MNetworkChangeCallback" => unsafe {
let name = mem
.read_utf8_lossy(network_value.value.name_ptr.address(), 4096)
.read_utf8_lossy(network_value.value.name_ptr.address(), 128)
.data_part()?;
ClassMetadata::NetworkChangeCallback { name }
@@ -208,7 +186,7 @@ fn read_class_binding_metadata(
let var_value = network_value.value.var_value;
let name = mem
.read_utf8_lossy(var_value.name.address(), 4096)
.read_utf8_lossy(var_value.name.address(), 128)
.data_part()?;
let type_name = mem
@@ -234,27 +212,19 @@ fn read_enum_binding(
let binding = mem.read_ptr(binding_ptr).data_part()?;
let name = mem
.read_utf8_lossy(binding.name.address(), 4096)
.read_utf8_lossy(binding.name.address(), 128)
.data_part()?;
if name.is_empty() {
bail!("empty enum name");
bail!("invalid enum name");
}
let members = read_enum_binding_members(mem, &binding)?;
debug!(
"found enum: {} at {:#X} (alignment: {}) (member count: {})",
name,
binding_ptr.to_umem(),
binding.align_of,
binding.size,
);
Ok(Enum {
name,
alignment: binding.align_of,
size: binding.enum_count,
alignment: binding.alignment,
size: binding.enumerator_count,
members,
})
}
@@ -263,15 +233,15 @@ fn read_enum_binding_members(
mem: &mut impl MemoryView,
binding: &SchemaEnumBinding,
) -> Result<Vec<EnumMember>> {
if binding.enums.is_null() {
if binding.enumerators.is_null() {
return Ok(Vec::new());
}
(0..binding.enum_count).try_fold(Vec::new(), |mut acc, i| {
let r#enum = mem.read_ptr(binding.enums.at(i as _)).data_part()?;
(0..binding.enumerator_count).try_fold(Vec::new(), |mut acc, i| {
let r#enum = mem.read_ptr(binding.enumerators.at(i as _)).data_part()?;
let name = mem
.read_utf8_lossy(r#enum.name.address(), 4096)
.read_utf8_lossy(r#enum.name.address(), 128)
.data_part()?;
acc.push(EnumMember {
@@ -296,15 +266,15 @@ fn read_schema_system<P: Process + MemoryView>(process: &mut P) -> Result<Schema
if !view
.scanner()
.finds_code(pattern!("488905${'} 4c8d0d${} 33c0 48c705[8] 8905"), &mut save)
.finds_code(pattern!("4c8d35${'} 0f2845"), &mut save)
{
bail!("outdated schema system pattern");
}
let schema_system: SchemaSystem = process.read(module.base + save[1]).data_part()?;
if schema_system.num_registrations == 0 {
bail!("no schema system registrations found");
if schema_system.registration_count == 0 {
bail!("no schema registrations");
}
Ok(schema_system)
@@ -316,7 +286,7 @@ fn read_type_scopes(
) -> Result<Vec<TypeScope>> {
let type_scopes = &schema_system.type_scopes;
(0..type_scopes.size).try_fold(Vec::new(), |mut acc, i| {
(0..type_scopes.count).try_fold(Vec::new(), |mut acc, i| {
let type_scope_ptr = type_scopes.element(mem, i as _)?;
let type_scope = mem.read_ptr(type_scope_ptr).data_part()?;
@@ -326,14 +296,14 @@ fn read_type_scopes(
let classes: Vec<_> = type_scope
.class_bindings
.elements(mem)?
.elements(mem)
.iter()
.filter_map(|ptr| read_class_binding(mem, *ptr).ok())
.collect();
let enums: Vec<_> = type_scope
.enum_bindings
.elements(mem)?
.elements(mem)
.iter()
.filter_map(|ptr| read_enum_binding(mem, *ptr).ok())
.collect();
@@ -343,9 +313,8 @@ fn read_type_scopes(
}
debug!(
"found type scope: {} at {:#X} (class count: {}) (enum count: {})",
"module \"{}\" contains {} class(es) and {} enum(s)",
module_name,
type_scope_ptr.to_umem(),
classes.len(),
enums.len(),
);

View File

@@ -1,3 +1,4 @@
#![feature(once_cell_try)]
#![allow(dead_code)]
#![allow(unused_imports)]
@@ -19,6 +20,7 @@ use simplelog::*;
use output::Output;
mod analysis;
mod memory;
mod output;
mod source2;

24
src/memory/address.rs Normal file
View File

@@ -0,0 +1,24 @@
use memflow::prelude::*;
#[inline]
pub fn follow_call(mem: &mut impl MemoryView, base: Address) -> Result<Address> {
rel32_target(mem, base, 0x1)
}
#[inline]
pub fn follow_jmp(mem: &mut impl MemoryView, base: Address) -> Result<Address> {
rel32_target(mem, base, 0x1)
}
#[inline]
pub fn resolve_rip(mem: &mut impl MemoryView, base: Address) -> Result<Address> {
rel32_target(mem, base, 0x3)
}
fn rel32_target(mem: &mut impl MemoryView, base: Address, offset: usize) -> Result<Address> {
let rel32: i32 = mem.read(base + offset).data_part()?; // RIP-relative displacement.
let instr_end = (base + offset + size_of::<i32>()).to_umem() as i64;
let target_addr = instr_end.wrapping_add(rel32 as i64);
Ok(target_addr.into())
}

1
src/memory/mod.rs Normal file
View File

@@ -0,0 +1 @@
pub mod address;

View File

@@ -20,7 +20,8 @@ impl CodeWriter for ButtonMap {
fn write_hpp(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
writeln!(fmt, "#pragma once\n")?;
writeln!(fmt, "#include <cstddef>\n")?;
writeln!(fmt, "#include <cstddef>")?;
writeln!(fmt, "#include <cstdint>\n")?;
fmt.block("namespace cs2_dumper", false, |fmt| {
writeln!(fmt, "// Module: client.dll")?;

View File

@@ -38,7 +38,8 @@ impl CodeWriter for InterfaceMap {
fn write_hpp(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
writeln!(fmt, "#pragma once\n")?;
writeln!(fmt, "#include <cstddef>\n")?;
writeln!(fmt, "#include <cstddef>")?;
writeln!(fmt, "#include <cstdint>\n")?;
fmt.block("namespace cs2_dumper", false, |fmt| {
fmt.block("namespace interfaces", false, |fmt| {

View File

@@ -29,7 +29,8 @@ impl CodeWriter for OffsetMap {
fn write_hpp(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
writeln!(fmt, "#pragma once\n")?;
writeln!(fmt, "#include <cstddef>\n")?;
writeln!(fmt, "#include <cstddef>")?;
writeln!(fmt, "#include <cstdint>\n")?;
fmt.block("namespace cs2_dumper", false, |fmt| {
fmt.block("namespace offsets", false, |fmt| {

View File

@@ -41,17 +41,17 @@ impl CodeWriter for SchemaMap {
.members
.iter()
.map(|member| {
let hex = if member.value < 0
|| member.value > i32::MAX as i64
{
format!(
"unchecked(({}){})",
type_name, member.value
)
} else {
format!("{:#X}", member.value)
};
format!("{} = {}", member.name, hex)
let formatted_value =
if (0..=i32::MAX as i64).contains(&member.value) {
format!("{:#X}", member.value)
} else {
format!(
"unchecked(({}){})",
type_name, member.value
)
};
format!("{} = {}", member.name, formatted_value)
})
.collect::<Vec<_>>()
.join(",\n");
@@ -63,10 +63,10 @@ impl CodeWriter for SchemaMap {
for class in classes {
let parent_name = class
.parent
.as_ref()
.map(|parent| slugify(&parent.name))
.unwrap_or_else(|| String::from("None"));
.parent_name
.as_deref()
.map(slugify)
.unwrap_or("None".to_string());
writeln!(fmt, "// Parent: {}", parent_name)?;
writeln!(fmt, "// Field count: {}", class.fields.len())?;
@@ -101,7 +101,8 @@ impl CodeWriter for SchemaMap {
fn write_hpp(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
writeln!(fmt, "#pragma once\n")?;
writeln!(fmt, "#include <cstddef>\n")?;
writeln!(fmt, "#include <cstddef>")?;
writeln!(fmt, "#include <cstdint>\n")?;
fmt.block("namespace cs2_dumper", false, |fmt| {
fmt.block("namespace schemas", false, |fmt| {
@@ -134,7 +135,23 @@ impl CodeWriter for SchemaMap {
.members
.iter()
.map(|member| {
format!("{} = {:#X}", member.name, member.value)
let formatted_value = if (0..=i32::MAX as i64)
.contains(&member.value)
{
format!("{:#X}", member.value)
} else {
let max_value = match type_name {
"uint8_t" => 0xFFu64,
"uint16_t" => 0xFFFFu64,
"uint32_t" => 0xFFFFFFFFu64,
"uint64_t" => 0xFFFFFFFFFFFFFFFFu64,
_ => 0,
};
format!("{:#X}", max_value)
};
format!("{} = {}", member.name, formatted_value)
})
.collect::<Vec<_>>()
.join(",\n");
@@ -146,10 +163,10 @@ impl CodeWriter for SchemaMap {
for class in classes {
let parent_name = class
.parent
.as_ref()
.map(|parent| slugify(&parent.name))
.unwrap_or_else(|| String::from("None"));
.parent_name
.as_deref()
.map(slugify)
.unwrap_or("None".to_string());
writeln!(fmt, "// Parent: {}", parent_name)?;
writeln!(fmt, "// Field count: {}", class.fields.len())?;
@@ -219,7 +236,7 @@ impl CodeWriter for SchemaMap {
(
slugify(&class.name),
json!({
"parent": class.parent.as_ref().map(|parent| &parent.name),
"parent": class.parent_name,
"fields": fields,
"metadata": metadata
}),
@@ -311,18 +328,21 @@ impl CodeWriter for SchemaMap {
.members
.iter()
.filter_map(|member| {
// Filter out duplicate values.
if used_values.insert(member.value) {
let value = if member.value == -1 {
format!("{}::MAX", type_name)
} else {
format!("{:#X}", member.value)
};
Some(format!("{} = {}", member.name, value))
} else {
None
// Skip duplicate values.
if !used_values.insert(member.value) {
return None;
}
let formatted_value = if member.value == -1 {
format!("{}::MAX", type_name)
} else {
format!("{:#X}", member.value)
};
Some(format!(
"{} = {}",
member.name, formatted_value
))
})
.collect::<Vec<_>>()
.join(",\n");
@@ -334,10 +354,10 @@ impl CodeWriter for SchemaMap {
for class in classes {
let parent_name = class
.parent
.as_ref()
.map(|parent| slugify(&parent.name))
.unwrap_or_else(|| String::from("None"));
.parent_name
.as_deref()
.map(slugify)
.unwrap_or("None".to_string());
writeln!(fmt, "// Parent: {}", parent_name)?;
writeln!(fmt, "// Field count: {}", class.fields.len())?;

View File

@@ -3,10 +3,10 @@ use memflow::prelude::v1::*;
#[derive(Pod)]
#[repr(C)]
pub struct KeyButton {
pad_0000: [u8; 0x8], // 0x0000
pad_0: [u8; 0x8], // 0x0000
pub name: Pointer64<ReprCString>, // 0x0008
pad_0010: [u8; 0x20], // 0x0010
pad_1: [u8; 0x20], // 0x0010
pub state: u32, // 0x0030
pad_0034: [u8; 0x54], // 0x0034
pad_2: [u8; 0x54], // 0x0034
pub next: Pointer64<KeyButton>, // 0x0088
}

View File

@@ -1,7 +1,9 @@
pub use client::*;
pub use schema_system::*;
pub use tier0::*;
pub use tier1::*;
pub mod client;
pub mod schema_system;
pub mod tier0;
pub mod tier1;

View File

@@ -1,18 +1,16 @@
use memflow::prelude::v1::*;
use super::SchemaClassInfoData;
#[repr(C)]
pub struct SchemaBaseClassInfoData {
pad_0000: [u8; 0x18], // 0x0000
pub prev: Pointer64<SchemaBaseClass>, // 0x0018
pad_0: [u8; 0x18], // 0x0000
pub class: Pointer64<SchemaBaseClass>, // 0x0018
}
unsafe impl Pod for SchemaBaseClassInfoData {}
#[repr(C)]
pub struct SchemaBaseClass {
pad_0000: [u8; 0x10], // 0x0000
pad_0: [u8; 0x10], // 0x0000
pub name: Pointer64<ReprCString>, // 0x0010
}

View File

@@ -14,16 +14,16 @@ pub struct SchemaClassInfoData {
pub size: i32, // 0x0018
pub field_count: i16, // 0x001C
pub static_metadata_count: i16, // 0x001E
pad_0020: [u8; 0x2], // 0x0020
pub align_of: u8, // 0x0022
pad_0: [u8; 0x2], // 0x0020
pub alignment: u8, // 0x0022
pub has_base_class: u8, // 0x0023
pub total_class_size: i16, // 0x0024
pub derived_class_size: i16, // 0x0026
pub fields: Pointer64<[SchemaClassFieldData]>, // 0x0028
pad_0038: [u8; 0x8], // 0x0030
pad_1: [u8; 0x8], // 0x0030
pub base_classes: Pointer64<SchemaBaseClassInfoData>, // 0x0038
pub static_metadata: Pointer64<[SchemaMetadataEntryData]>, // 0x0040
pub type_scope: Pointer64<SchemaSystemTypeScope>, // 0x0050
pub r#type: Pointer64<SchemaType>, // 0x0058
pad_0060: [u8; 0x10], // 0x0060
pad_2: [u8; 0x10], // 0x0060
}

View File

@@ -12,13 +12,14 @@ pub struct SchemaEnumInfoData {
pub name: Pointer64<ReprCString>, // 0x0008
pub module_name: Pointer64<ReprCString>, // 0x0010
pub size: u8, // 0x0018
pub align_of: u8, // 0x0019
pad_001a: [u8; 0x2], // 0x001A
pub enum_count: u16, // 0x001C
pub alignment: u8, // 0x0019
pub flags: u8, // 0x001A
pad_0: [u8; 0x1], // 0x001B
pub enumerator_count: u16, // 0x001C
pub static_metadata_count: u16, // 0x001E
pub enums: Pointer64<[SchemaEnumeratorInfoData]>, // 0x0020
pub enumerators: Pointer64<[SchemaEnumeratorInfoData]>, // 0x0020
pub static_metadata: Pointer64<SchemaMetadataEntryData>, // 0x0028
pub type_scope: Pointer64<SchemaSystemTypeScope>, // 0x0030
pub min_enum_value: i64, // 0x0038
pub max_enum_value: i64, // 0x0040
pub min_enumerator_value: i64, // 0x0038
pub max_enumerator_value: i64, // 0x0040
}

View File

@@ -8,7 +8,7 @@ pub struct SchemaEnumeratorInfoData {
pub name: Pointer64<ReprCString>, // 0x0000
pub value: SchemaEnumeratorInfoDataUnion, // 0x0008
pub metadata_count: i32, // 0x0010
pad_0014: [u8; 0x4], // 0x0014
pad_0: [u8; 0x4], // 0x0014
pub metadata: Pointer64<SchemaMetadataEntryData>, // 0x0018
}

View File

@@ -20,7 +20,7 @@ pub union SchemaNetworkValueUnion {
pub name_ptr: Pointer64<ReprCString>,
pub int_value: i32,
pub float_value: f32,
pub ptr: Pointer64<()>,
pub ptr_value: Pointer64<()>,
pub var_value: SchemaVarName,
pub name_value: [c_char; 32],
}

View File

@@ -6,10 +6,10 @@ use crate::source2::UtlVector;
#[repr(C)]
pub struct SchemaSystem {
pad_0000: [u8; 0x190], // 0x0000
pad_0: [u8; 0x190], // 0x0000
pub type_scopes: UtlVector<Pointer64<SchemaSystemTypeScope>>, // 0x0190
pad_0198: [u8; 0xE0], // 0x01A0
pub num_registrations: i32, // 0x0280
pad_1: [u8; 0xE0], // 0x01A0
pub registration_count: i32, // 0x0280
}
unsafe impl Pod for SchemaSystem {}

View File

@@ -9,10 +9,10 @@ use crate::source2::UtlTsHash;
#[derive(Pod)]
#[repr(C)]
pub struct SchemaSystemTypeScope {
pad_0000: [u8; 0x8], // 0x0000
pad_0: [u8; 0x8], // 0x0000
pub name: [c_char; 256], // 0x0008
pub global_scope: Pointer64<SchemaSystemTypeScope>, // 0x0108
pad_0110: [u8; 0x430], // 0x0110
pub class_bindings: UtlTsHash<SchemaClassBinding>, // 0x0540
pad_1: [u8; 0x450], // 0x0110
pub class_bindings: UtlTsHash<SchemaClassBinding>, // 0x0560
pub enum_bindings: UtlTsHash<SchemaEnumBinding>, // 0x1DD0
}

View File

@@ -30,36 +30,36 @@ pub enum SchemaTypeCategory {
#[repr(C)]
pub struct SchemaArrayT {
pub array_size: u32, // 0x0000
pad_0004: [u8; 0x4], // 0x0004
pad_0: [u8; 0x4], // 0x0004
pub element: Pointer64<SchemaType>, // 0x0008
}
#[derive(Clone, Copy)]
#[repr(C)]
pub struct SchemaAtomicI {
pad_0000: [u8; 0x10], // 0x0000
pub value: u64, // 0x0010
pad_0: [u8; 0x10], // 0x0000
pub value: u64, // 0x0010
}
#[derive(Clone, Copy)]
#[repr(C)]
pub struct SchemaAtomicT {
pub element: Pointer64<SchemaType>, // 0x0000
pad_0008: [u8; 0x8], // 0x0008
pad_0: [u8; 0x8], // 0x0008
pub template: Pointer64<SchemaType>, // 0x0010
}
#[derive(Clone, Copy)]
#[repr(C)]
pub struct SchemaAtomicTT {
pad_0000: [u8; 0x10], // 0x0000
pad_0: [u8; 0x10], // 0x0000
pub templates: [Pointer64<SchemaType>; 2], // 0x0010
}
#[derive(Clone, Copy)]
#[repr(C)]
pub struct SchemaAtomicTF {
pad_0000: [u8; 0x10], // 0x0000
pad_0: [u8; 0x10], // 0x0000
pub template: Pointer64<SchemaType>, // 0x0010
pub size: i32, // 0x0018
}
@@ -67,14 +67,14 @@ pub struct SchemaAtomicTF {
#[derive(Clone, Copy)]
#[repr(C)]
pub struct SchemaAtomicTTF {
pad_0000: [u8; 0x10], // 0x0000
pad_0: [u8; 0x10], // 0x0000
pub templates: [Pointer64<SchemaType>; 2], // 0x0010
pub size: i32, // 0x0020
}
#[repr(C)]
pub struct SchemaType {
pad_0000: [u8; 0x8], // 0x0000
pad_0: [u8; 0x8], // 0x0000
pub name: Pointer64<ReprCString>, // 0x0008
pub type_scope: Pointer64<SchemaSystemTypeScope>, // 0x0010
pub type_category: SchemaTypeCategory, // 0x0018

3
src/source2/tier0/mod.rs Normal file
View File

@@ -0,0 +1,3 @@
pub use ts_list::*;
pub mod ts_list;

View File

@@ -0,0 +1,14 @@
use memflow::types::Pointer64;
#[repr(C)]
pub struct TsListNode;
#[repr(C)]
pub struct TsListHead {
pub next: Pointer64<TsListNode>, // 0x0000
}
#[repr(C)]
pub struct TsListBase {
pub head: TsListHead, // 0x0000
}

View File

@@ -1,8 +1,8 @@
pub use interface::InterfaceReg;
pub use utl_memory::UtlMemory;
pub use utl_memory_pool::UtlMemoryPoolBase;
pub use utl_ts_hash::UtlTsHash;
pub use utl_vector::UtlVector;
pub use interface::*;
pub use utl_memory::*;
pub use utl_memory_pool::*;
pub use utl_ts_hash::*;
pub use utl_vector::*;
pub mod interface;
pub mod utl_memory;

View File

@@ -2,9 +2,9 @@ use memflow::prelude::v1::*;
#[repr(C)]
pub struct UtlMemory<T> {
pub mem: Pointer64<[T]>, // 0x0000
pub alloc_count: i32, // 0x0008
pub grow_size: i32, // 0x000C
pub data: Pointer64<[T]>, // 0x0000
pub count: i32, // 0x0008
pub grow_size: i32, // 0x000C
}
impl<T: Pod> UtlMemory<T> {
@@ -14,10 +14,10 @@ impl<T: Pod> UtlMemory<T> {
}
pub fn element(&self, mem: &mut impl MemoryView, index: usize) -> Result<T> {
if index >= self.alloc_count as usize {
if index >= self.count as usize {
return Err(ErrorKind::OutOfBounds.into());
}
mem.read_ptr(self.mem.at(index as _)).data_part()
mem.read_ptr(self.data.at(index as _)).data_part()
}
}

View File

@@ -1,40 +1,36 @@
use memflow::prelude::v1::*;
use crate::source2::TsListBase;
#[repr(u32)]
pub enum MemoryPoolGrowType {
None = 0,
Fast,
Slow,
None = 0, // Doesn't allocate new blobs.
Fast, // New blobs will grow in size.
Slow, // New blobs will stay the same size.
}
#[derive(Pod)]
#[repr(C)]
pub struct Blob {
pub next: Pointer64<Blob>, // 0x0000
pub num_bytes: i32, // 0x0008
pub data: [u8; 1], // 0x000C
pad_000d: [u8; 3], // 0x000D
}
#[derive(Pod)]
#[repr(C)]
pub struct FreeList {
pub next: Pointer64<FreeList>, // 0x0000
pub struct UtlMemoryPoolBlob {
pub next: Pointer64<UtlMemoryPoolBlob>, // 0x0000
pub size: i32, // 0x0008
pub data: [u8; 1], // 0x000C
pad_0: [u8; 0x3], // 0x000D
}
#[repr(C)]
pub struct UtlMemoryPoolBase {
pub block_size: i32, // 0x0000
pub blocks_per_blob: i32, // 0x0004
pub grow_mode: MemoryPoolGrowType, // 0x0008
pub blocks_alloc: i32, // 0x000C
pub peak_alloc: i32, // 0x0010
pub align_of: u16, // 0x0014
pub blob_count: u16, // 0x0016
pub free_list_tail: Pointer64<Pointer64<FreeList>>, // 0x0018
pub free_list_head: Pointer64<FreeList>, // 0x0020
pad_0028: [u8; 0x44], // 0x0028
pub blob_head: Pointer64<Blob>, // 0x0070
pub total_size: i32, // 0x0078
pad_007c: [u8; 0x4], // 0x007C
pub struct UtlMemoryPool {
pub block_size: i32, // 0x0000
pub blocks_per_blob: i32, // 0x0004
pub grow_mode: MemoryPoolGrowType, // 0x0008
pub blocks_allocated: i32, // 0x000C
pub peak_allocated: i32, // 0x0010
pub alignment: u16, // 0x0014
pub blob_count: u16, // 0x0016
pad_0: [u8; 0x2], // 0x0018
pub free_blocks: TsListBase, // 0x0020
pad_1: [u8; 0x20], // 0x0028
pub blob_head: Pointer64<UtlMemoryPoolBlob>, // 0x0048
pub total_size: i32, // 0x0050
pad_2: [u8; 0xC], // 0x0054
}

View File

@@ -1,109 +1,119 @@
use std::collections::HashSet;
use memflow::prelude::v1::*;
use super::UtlMemoryPoolBase;
use super::UtlMemoryPool;
#[repr(C)]
pub struct HashAllocatedBlob<D> {
pub next: Pointer64<HashAllocatedBlob<D>>, // 0x0000
pad_0008: [u8; 0x8], // 0x0008
pub data: Pointer64<D>, // 0x0010
pad_0018: [u8; 0x8], // 0x0018
pub struct UtlTsHashAllocatedBlob<D> {
pub next: Pointer64<UtlTsHashAllocatedBlob<D>>, // 0x0000
pad_0: [u8; 0x8], // 0x0008
pub data: Pointer64<D>, // 0x0010
pad_1: [u8; 0x18], // 0x0018
}
unsafe impl<D: 'static> Pod for HashAllocatedBlob<D> {}
unsafe impl<D: 'static> Pod for UtlTsHashAllocatedBlob<D> {}
#[repr(C)]
pub struct RTL_SRWLOCK
{
pub struct UtlTsHashFixedData<D, K> {
pub ui_key: K, // 0x0000
pub next: Pointer64<UtlTsHashFixedData<D, K>>, // 0x0008
pub data: Pointer64<D>, // 0x0010
}
unsafe impl<D: 'static, K: 'static> Pod for UtlTsHashFixedData<D, K> {}
#[repr(C)]
pub struct HashBucket<D, K> {
pub lock: Pointer64<RTL_SRWLOCK>, // 0x0000
pub first: Pointer64<HashFixedDataInternal<D, K>>, // 0x0008
pub first_uncommitted: Pointer64<HashFixedDataInternal<D, K>>, // 0x0010
pub struct UtlTsHashBucket<D, K> {
pub add_lock: usize, // 0x0000
pub first: Pointer64<UtlTsHashFixedData<D, K>>, // 0x0008
pub first_uncommitted: Pointer64<UtlTsHashFixedData<D, K>>, // 0x0010
}
#[repr(C)]
pub struct HashFixedDataInternal<D, K> {
pub ui_key: K, // 0x0000
pub next: Pointer64<HashFixedDataInternal<D, K>>, // 0x0008
pub data: Pointer64<D>, // 0x0010
}
unsafe impl<D: 'static, K: 'static> Pod for HashFixedDataInternal<D, K> {}
#[repr(C)]
pub struct UtlTsHash<D, const C: usize = 256, K = u64> {
pub entry_mem: UtlMemoryPoolBase, // 0x0000
pub buckets: [HashBucket<D, K>; C], // 0x0080
pub needs_commit: bool, // 0x1880
pad_2881: [u8; 0xF], // 0x1881
pub entry_mem: UtlMemoryPool, // 0x0000
pub buckets: [UtlTsHashBucket<D, K>; C], // 0x0060
pub needs_commit: bool, // 0x1860
pad_0: [u8; 0x3], // 0x1861
pub contention_check: i32, // 0x1864
pad_1: [u8; 0x8], // 0x1868
}
impl<D: Pod, const C: usize, K: Pod> UtlTsHash<D, C, K> {
#[inline]
pub fn blocks_alloc(&self) -> i32 {
self.entry_mem.blocks_alloc
pub fn elements(&self, mem: &mut impl MemoryView) -> Vec<Pointer64<D>> {
let allocated = self.allocated_elements(mem);
let unallocated = self.unallocated_elements(mem);
let mut result = Vec::with_capacity(allocated.len() + unallocated.len());
result.extend(allocated);
result.extend(unallocated);
let mut seen = HashSet::with_capacity(result.capacity());
// Remove duplicate pointers that exist in both lists.
result.retain(|ptr| seen.insert(ptr.address().to_umem()));
result
}
#[inline]
pub fn block_size(&self) -> i32 {
self.entry_mem.block_size
}
fn allocated_elements(&self, mem: &mut impl MemoryView) -> Vec<Pointer64<D>> {
let used_count = self.entry_mem.blocks_allocated as usize;
#[inline]
pub fn peak_count(&self) -> i32 {
self.entry_mem.peak_alloc
}
pub fn elements(&self, mem: &mut impl MemoryView) -> Result<Vec<Pointer64<D>>> {
let blocks_alloc = self.blocks_alloc() as usize;
let peak_alloc = self.peak_count() as usize;
let mut allocated_list = Vec::with_capacity(peak_alloc);
let mut unallocated_list = Vec::with_capacity(blocks_alloc);
let mut elements = Vec::with_capacity(used_count);
for bucket in &self.buckets {
let mut cur_element = bucket.first_uncommitted;
let mut node_ptr = bucket.first_uncommitted;
while !cur_element.is_null() {
let element = mem.read_ptr(cur_element).data_part()?;
while !node_ptr.is_null() {
let node = match mem.read_ptr(node_ptr).data_part() {
Ok(n) => n,
Err(_) => break,
};
if !element.data.is_null() {
allocated_list.push(element.data);
if !node.data.is_null() {
elements.push(node.data);
}
if allocated_list.len() >= blocks_alloc {
if elements.len() >= used_count {
break;
}
cur_element = element.next;
node_ptr = node.next;
}
}
let mut cur_blob =
Pointer64::<HashAllocatedBlob<D>>::from(self.entry_mem.free_list_head.address());
elements
}
while !cur_blob.is_null() {
let blob = mem.read_ptr(cur_blob).data_part()?;
fn unallocated_elements(&self, mem: &mut impl MemoryView) -> Vec<Pointer64<D>> {
let free_count = self.entry_mem.peak_allocated as usize;
let mut elements = Vec::with_capacity(free_count);
let mut blob_ptr = Pointer64::<UtlTsHashAllocatedBlob<D>>::from(
self.entry_mem.free_blocks.head.next.address(),
);
while !blob_ptr.is_null() {
let blob = match mem.read_ptr(blob_ptr).data_part() {
Ok(b) => b,
Err(_) => break,
};
if !blob.data.is_null() {
unallocated_list.push(blob.data);
elements.push(blob.data);
}
if unallocated_list.len() >= peak_alloc {
if elements.len() >= free_count {
break;
}
cur_blob = blob.next;
blob_ptr = blob.next;
}
Ok(if unallocated_list.len() > allocated_list.len() {
unallocated_list
} else {
allocated_list
})
elements
}
}

View File

@@ -2,18 +2,18 @@ use memflow::prelude::v1::*;
#[repr(C)]
pub struct UtlVector<T> {
pub size: i32, // 0x0000
pad_0004: [u8; 0x4], // 0x0004
pub mem: Pointer64<[T]>, // 0x0008
pub count: i32, // 0x0000
pad_0: [u8; 0x4], // 0x0004
pub data: Pointer64<[T]>, // 0x0008
}
impl<T: Pod> UtlVector<T> {
pub fn element(&self, mem: &mut impl MemoryView, index: usize) -> Result<T> {
if index >= self.size as usize {
if index >= self.count as usize {
return Err(ErrorKind::OutOfBounds.into());
}
mem.read_ptr(self.mem.at(index as _)).data_part()
mem.read_ptr(self.data.at(index as _)).data_part()
}
}