mirror of
https://github.com/a2x/cs2-dumper.git
synced 2025-04-12 00:15:35 +08:00
421 lines
13 KiB
Rust
421 lines
13 KiB
Rust
use std::fs::File;
|
|
|
|
use anyhow::Result;
|
|
|
|
use simplelog::{debug, error, info};
|
|
|
|
use super::{generate_files, Entries, Entry};
|
|
|
|
use crate::builder::FileBuilderEnum;
|
|
use crate::config::Operation::*;
|
|
use crate::config::{self, Config};
|
|
use crate::os::Process;
|
|
|
|
pub fn dump_offsets(
|
|
process: &Process,
|
|
builders: &mut Vec<FileBuilderEnum>,
|
|
file_path: &str,
|
|
indent: usize,
|
|
) -> Result<()> {
|
|
let file = File::open(config::OFFSETS_CONF)?;
|
|
|
|
let config: Config = serde_json::from_reader(file)?;
|
|
|
|
info!("Dumping offsets...");
|
|
|
|
let mut entries = Entries::new();
|
|
|
|
for signature in config.signatures {
|
|
debug!("Searching for <bright-yellow>{}</>...", signature.name);
|
|
|
|
let module = process
|
|
.get_module_by_name(&signature.module)
|
|
.expect(&format!("Failed to find module {}.", signature.module));
|
|
|
|
let mut address = match process.find_pattern(&signature.module, &signature.pattern) {
|
|
Some(a) => a,
|
|
None => {
|
|
error!(
|
|
"Failed to find pattern for <bright-yellow>{}</>.",
|
|
signature.name
|
|
);
|
|
|
|
continue;
|
|
}
|
|
};
|
|
|
|
for operation in signature.operations {
|
|
match operation {
|
|
Add { value } => address += value,
|
|
Deref { times, size } => {
|
|
let times = times.unwrap_or(1);
|
|
let size = size.unwrap_or(8);
|
|
|
|
for _ in 0..times {
|
|
process.read_memory_raw(address, &mut address as *mut _ as *mut _, size)?;
|
|
}
|
|
}
|
|
Jmp { offset, length } => {
|
|
address = process.resolve_jmp(address, offset, length)?.into();
|
|
}
|
|
Rip { offset, length } => {
|
|
address = process.resolve_rip(address, offset, length)?.into()
|
|
}
|
|
Slice { start, end } => {
|
|
let mut result: usize = 0;
|
|
|
|
process.read_memory_raw(
|
|
address + start,
|
|
&mut result as *mut _ as *mut _,
|
|
end - start,
|
|
)?;
|
|
|
|
address = result.into();
|
|
}
|
|
Sub { value } => address -= value,
|
|
}
|
|
}
|
|
|
|
let (name, value) = if address < module.base() {
|
|
debug!(
|
|
"Found <bright-yellow>{}</> @ <bright-blue>{:#X}</>",
|
|
signature.name, address
|
|
);
|
|
|
|
(signature.name, address)
|
|
} else {
|
|
debug!(
|
|
"Found <bright-yellow>{}</> @ <bright-magenta>{:#X}</> (<blue>{}</> + <bright-blue>{:#X}</>)",
|
|
signature.name,
|
|
address,
|
|
signature.module,
|
|
address - module.base()
|
|
);
|
|
|
|
(signature.name, address - module.base())
|
|
};
|
|
|
|
if name == "dwBuildNumber" {
|
|
let build_number = process.read_memory::<u32>(module.base() + value)?;
|
|
|
|
debug!("Game build number: <bright-yellow>{}</>", build_number);
|
|
|
|
let container = entries.entry("game_info".to_string()).or_default();
|
|
|
|
container.comment =
|
|
Some("Some additional information about the game at dump time".to_string());
|
|
|
|
container.data.push(Entry {
|
|
name: "buildNumber".to_string(),
|
|
value: build_number as usize,
|
|
comment: Some("Game build number".to_string()),
|
|
indent: Some(indent),
|
|
});
|
|
}
|
|
|
|
let container = entries
|
|
.entry(signature.module.replace(".", "_"))
|
|
.or_default();
|
|
|
|
container.comment = Some(signature.module);
|
|
|
|
container.data.push(Entry {
|
|
name,
|
|
value,
|
|
comment: None,
|
|
indent: Some(indent),
|
|
});
|
|
}
|
|
|
|
generate_files(builders, &entries, file_path, "offsets")?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
use std::ffi::{c_char, c_void};
|
|
use std::fs;
|
|
use std::mem::offset_of;
|
|
use std::thread;
|
|
use std::time::Duration;
|
|
|
|
use core::arch::x86_64::_bittest;
|
|
|
|
use anyhow::anyhow;
|
|
|
|
use serde_json::Value;
|
|
|
|
fn read_json_value(file_path: &str) -> Result<Value> {
|
|
let content = fs::read_to_string(file_path)?;
|
|
|
|
serde_json::from_str(&content).map_err(Into::into)
|
|
}
|
|
|
|
fn get_class_field(module_name: &str, class_name: &str, class_key: &str) -> Result<u64> {
|
|
let value = read_json_value(&format!("generated/{}.json", module_name))
|
|
.expect("unable to read json file");
|
|
|
|
value[class_name]["data"][class_key]["value"]
|
|
.as_u64()
|
|
.ok_or_else(|| {
|
|
anyhow!(
|
|
"unable to find class field {} in class {}",
|
|
class_key,
|
|
class_name
|
|
)
|
|
})
|
|
}
|
|
|
|
fn get_offset_value(module_name: &str, offset_name: &str) -> Result<u64> {
|
|
let value = read_json_value("generated/offsets.json").expect("unable to read offsets.json");
|
|
|
|
value[module_name.replace(".", "_")]["data"][offset_name]["value"]
|
|
.as_u64()
|
|
.ok_or_else(|| anyhow!("unable to find offset"))
|
|
}
|
|
|
|
#[test]
|
|
fn build_number() -> Result<()> {
|
|
let process = Process::new("cs2.exe")?;
|
|
|
|
let engine_base = process
|
|
.get_module_by_name("engine2.dll")
|
|
.expect("unable to find engine2.dll")
|
|
.base();
|
|
|
|
let build_number_offset = get_offset_value("engine2.dll", "dwBuildNumber")?;
|
|
|
|
let build_number =
|
|
process.read_memory::<u32>(engine_base + build_number_offset as usize)?;
|
|
|
|
println!("build number: {}", build_number);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn key_buttons() -> Result<()> {
|
|
let process = Process::new("cs2.exe")?;
|
|
|
|
let client_base = process
|
|
.get_module_by_name("client.dll")
|
|
.expect("unable to find client.dll")
|
|
.base();
|
|
|
|
const KEY_BUTTONS: [&str; 8] = [
|
|
"dwForceAttack",
|
|
"dwForceAttack2",
|
|
"dwForceBackward",
|
|
"dwForceCrouch",
|
|
"dwForceForward",
|
|
"dwForceJump",
|
|
"dwForceLeft",
|
|
"dwForceRight",
|
|
];
|
|
|
|
let get_key_state = |value: u32| match value {
|
|
256 => "key up",
|
|
65537 => "key down",
|
|
_ => "unknown",
|
|
};
|
|
|
|
// Sleep for a second, so we're able to test.
|
|
thread::sleep(Duration::from_secs(1));
|
|
|
|
for button in &KEY_BUTTONS {
|
|
let offset = get_offset_value("client.dll", button).expect("unable to find client.dll");
|
|
|
|
let value = process.read_memory::<u32>(client_base + offset as usize)?;
|
|
|
|
println!("key button: {} (state: {})", button, get_key_state(value));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn global_vars() -> Result<()> {
|
|
#[derive(Debug)]
|
|
#[repr(C)]
|
|
struct GlobalVarsBase {
|
|
real_time: f32, // 0x0000
|
|
frame_count: i32, // 0x0004
|
|
frame_time: f32, // 0x0008
|
|
absolute_frame_time: f32, // 0x000C
|
|
max_clients: i32, // 0x0010
|
|
pad_0: [u8; 0x14], // 0x0014
|
|
frame_time_2: f32, // 0x0028
|
|
current_time: f32, // 0x002C
|
|
current_time_2: f32, // 0x0030
|
|
pad_1: [u8; 0xC], // 0x0034
|
|
tick_count: f32, // 0x0040
|
|
pad_2: [u8; 0x4], // 0x0044
|
|
network_channel: *const c_void, // 0x0048
|
|
pad_3: [u8; 0x130], // 0x0050
|
|
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")
|
|
.expect("unable to find client.dll")
|
|
.base();
|
|
|
|
let global_vars_offset = get_offset_value("client.dll", "dwGlobalVars")?;
|
|
|
|
let global_vars = process
|
|
.read_memory::<*const GlobalVarsBase>(client_base + global_vars_offset as usize)?;
|
|
|
|
let current_map_name = unsafe {
|
|
(*global_vars)
|
|
.current_map_name(&process)
|
|
.unwrap_or_default()
|
|
};
|
|
|
|
println!("current map name: {}", current_map_name);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn is_key_down() -> Result<()> {
|
|
let process = Process::new("cs2.exe")?;
|
|
|
|
let input_system_base = process
|
|
.get_module_by_name("inputsystem.dll")
|
|
.expect("unable to find inputsystem.dll")
|
|
.base();
|
|
|
|
let input_system_offset = get_offset_value("inputsystem.dll", "dwInputSystem")?;
|
|
|
|
let input_system = input_system_base + input_system_offset as usize;
|
|
|
|
let is_key_down = |key_code: i32| -> bool {
|
|
let element = process
|
|
.read_memory::<i32>((input_system + 0x4 * (key_code as usize / 32) + 0x12A0).into())
|
|
.unwrap_or_default();
|
|
|
|
unsafe { _bittest(&element, key_code & 0x1F) != 0 }
|
|
};
|
|
|
|
// Sleep for a second, so we're able to test.
|
|
thread::sleep(Duration::from_secs(1));
|
|
|
|
// See https://www.unknowncheats.me/forum/3855779-post889.html for button codes.
|
|
println!("insert key down: {}", is_key_down(73));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn local_player_controller() -> Result<()> {
|
|
let process = Process::new("cs2.exe")?;
|
|
|
|
let client_base = process
|
|
.get_module_by_name("client.dll")
|
|
.expect("unable to find client.dll")
|
|
.base();
|
|
|
|
let local_player_controller_offset =
|
|
get_offset_value("client.dll", "dwLocalPlayerController")?;
|
|
|
|
let player_name_offset =
|
|
get_class_field("client.dll", "CBasePlayerController", "m_iszPlayerName")?;
|
|
|
|
let local_player_controller =
|
|
process.read_memory::<usize>(client_base + local_player_controller_offset as usize)?;
|
|
|
|
let player_name =
|
|
process.read_string((local_player_controller + player_name_offset as usize).into())?;
|
|
|
|
println!("local player name: {}", player_name);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn local_player_pawn() -> Result<()> {
|
|
#[derive(Debug)]
|
|
#[repr(C)]
|
|
struct Vector3D {
|
|
x: f32,
|
|
y: f32,
|
|
z: f32,
|
|
}
|
|
|
|
let process = Process::new("cs2.exe")?;
|
|
|
|
let client_base = process
|
|
.get_module_by_name("client.dll")
|
|
.expect("unable to find client.dll")
|
|
.base();
|
|
|
|
let local_player_pawn_offset = get_offset_value("client.dll", "dwLocalPlayerPawn")?;
|
|
|
|
let game_scene_node_offset =
|
|
get_class_field("client.dll", "C_BaseEntity", "m_pGameSceneNode")?;
|
|
|
|
let absolute_origin_offset =
|
|
get_class_field("client.dll", "CGameSceneNode", "m_vecAbsOrigin")?;
|
|
|
|
let local_player_pawn =
|
|
process.read_memory::<usize>(client_base + local_player_pawn_offset as usize)?;
|
|
|
|
let game_scene_node = process
|
|
.read_memory::<usize>((local_player_pawn + game_scene_node_offset as usize).into())?;
|
|
|
|
let absolute_origin = process
|
|
.read_memory::<Vector3D>((game_scene_node + absolute_origin_offset as usize).into())?;
|
|
|
|
println!("local player origin: {:?}", absolute_origin);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn window_size() -> Result<()> {
|
|
let process = Process::new("cs2.exe")?;
|
|
|
|
let engine_base = process
|
|
.get_module_by_name("engine2.dll")
|
|
.expect("unable to find engine2.dll")
|
|
.base();
|
|
|
|
let window_width_offset = get_offset_value("engine2.dll", "dwWindowWidth")?;
|
|
let window_height_offset = get_offset_value("engine2.dll", "dwWindowHeight")?;
|
|
|
|
let window_width =
|
|
process.read_memory::<u32>(engine_base + window_width_offset as usize)?;
|
|
|
|
let window_height =
|
|
process.read_memory::<u32>(engine_base + window_height_offset as usize)?;
|
|
|
|
println!("window size: {}x{}", window_width, window_height);
|
|
|
|
Ok(())
|
|
}
|
|
}
|