diff --git a/README.md b/README.md index 3d9bca6..b4db2ae 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # cs2-dumper -An external offset/interface dumper for Counter-Strike 2, with support for both Windows & Linux. Powered by [memflow](https://github.com/memflow/memflow). +An external offset/interface dumper for Counter-Strike 2, with support for both Windows & Linux. Powered +by [memflow](https://github.com/memflow/memflow). The native Linux version is available in the [linux](https://github.com/a2x/cs2-dumper/tree/linux) branch. @@ -15,7 +16,10 @@ toolchain must be installed. 1. Ensure the game process is running (Being in the main menu should suffice). 2. Run the `cs2-dumper` executable (Note that some memflow connectors may require elevated privileges). -When running the executable without providing an optional memflow connector name, it will default to using the [memflow-native](https://github.com/memflow/memflow-native) cross-platform OS layer to read the memory of the game process. If you wish to use an existing memflow connector instead, pass the `connector` and optional `connector-args` arguments to the program. +_Note:_ If you run the executable without specifying an optional memflow connector name, it will automatically use the +[memflow-native](https://github.com/memflow/memflow-native) OS layer to read the memory of the game process. If you +wish to use an existing memflow connector instead, you can pass the `connector` and optional `connector-args` arguments +to the program. E.g. `./cs2-dumper -c pcileech -a device=fpga -vvv` @@ -31,6 +35,10 @@ E.g. `./cs2-dumper -c pcileech -a device=fpga -vvv` - `-h, --help`: Print help. - `-V, --version`: Print version. +## Running Tests + +To run tests, use the following command: `cargo test -- --nocapture`. + ## License Licensed under the MIT license ([LICENSE](./LICENSE)). diff --git a/src/analysis/offsets.rs b/src/analysis/offsets.rs index 8c7f8d6..ba6cb43 100644 --- a/src/analysis/offsets.rs +++ b/src/analysis/offsets.rs @@ -148,3 +148,167 @@ pub fn offsets(process: &mut IntoProcessInstanceArcBox<'_>) -> Result Ok(map) } + +#[cfg(test)] +mod tests { + use std::fs; + + use serde_json::Value; + + use super::*; + + fn setup() -> Result> { + let os = memflow_native::create_os(&OsArgs::default(), LibArc::default())?; + + let process = os.into_process_by_name("cs2.exe")?; + + Ok(process) + } + + fn get_class_field_value(module_name: &str, class_name: &str, field_name: &str) -> Option { + let content = fs::read_to_string(format!("output/{}.json", module_name)).ok()?; + let value: Value = serde_json::from_str(&content).ok()?; + + value + .get(module_name)? + .get("classes")? + .get(class_name)? + .get("fields")? + .get(field_name)? + .as_u64() + } + + fn get_offset_value(module_name: &str, offset_name: &str) -> Option { + let content = fs::read_to_string("output/offsets.json").ok()?; + let value: Value = serde_json::from_str(&content).ok()?; + + let offset = value.get(module_name)?.get(offset_name)?; + + 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 + 0x1B8).into()) + .data_part()?; + + process.read_char_string(addr).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_char_string((local_player_controller + player_name_offset).into()) + .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(()) + } +} diff --git a/src/main.rs b/src/main.rs index 5e0016b..1023d79 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ use log::{info, Level}; use memflow::prelude::v1::*; -use simplelog::{ColorChoice, TermLogger}; +use simplelog::{ColorChoice, Config, TermLogger, TerminalMode}; use error::Result; use output::Output; @@ -65,8 +65,8 @@ fn main() -> Result<()> { TermLogger::init( log_level.to_level_filter(), - Default::default(), - Default::default(), + Config::default(), + TerminalMode::Mixed, ColorChoice::Auto, ) .unwrap(); @@ -93,6 +93,7 @@ fn main() -> Result<()> { let mut process = os.into_process_by_name(&args.process_name)?; 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)?;