From f186b19255d521de9d3346fcfce25b900be8b3f6 Mon Sep 17 00:00:00 2001 From: Janek <development@superyu.xyz> Date: Fri, 5 Jan 2024 00:31:11 +0100 Subject: [PATCH] optimize (not done yet) - alpha-alpha implementation of CachedView in csflow. - MUCH faster entity cache build time thanks to two professionally engineered functions :sunglasses: --- .vscode/launch.json | 26 ++++ Cargo.lock | 6 +- csflow/Cargo.toml | 2 +- csflow/src/context/cached_view.rs | 122 ++++++++++++++++++ csflow/src/{context.rs => context/mod.rs} | 43 ++++-- csflow/src/error.rs | 5 +- csflow/src/structs/entity/base_entity.rs | 44 ++++++- .../src/structs/entity/player_controller.rs | 39 +++++- csflow/src/structs/entity/player_pawn.rs | 26 ++-- csflow/src/structs/gamerules.rs | 8 +- csflow/src/structs/global_vars.rs | 5 +- csflow/src/traits/base_entity.rs | 2 + radarflow/src/dma/cache.rs | 105 ++++++++++++++- radarflow/src/dma/mod.rs | 113 +++++----------- 14 files changed, 417 insertions(+), 129 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 csflow/src/context/cached_view.rs rename csflow/src/{context.rs => context/mod.rs} (66%) diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..f2f68ed --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,26 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'radarflow'", + "cargo": { + "args": [ + "build", + "--bin=radarflow", + "--package=radarflow" + ], + "filter": { + "name": "radarflow", + "kind": "bin" + } + }, + "args": ["-p", "8081", "-c", "pcileech"], + "cwd": "${workspaceFolder}" + }, + ] +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index c0c773f..f1d9933 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1073,8 +1073,7 @@ checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "memflow" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e4ab23adc6b68aa85d9d2d43c04f31c51b895acb323f807ff95ed8d844639b1" +source = "git+https://github.com/memflow/memflow.git?rev=c19d870#c19d8707ce980497204ca446fc9565bc344fcc64" dependencies = [ "abi_stable", "bitflags 1.3.2", @@ -1104,8 +1103,7 @@ dependencies = [ [[package]] name = "memflow-derive" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d766f6681f968c92eb0359fc4bc99039ebe2568df4bb884c7cb7b16023e94d32" +source = "git+https://github.com/memflow/memflow.git?rev=c19d870#c19d8707ce980497204ca446fc9565bc344fcc64" dependencies = [ "darling", "proc-macro-crate", diff --git a/csflow/Cargo.toml b/csflow/Cargo.toml index 86255c1..41415e9 100644 --- a/csflow/Cargo.toml +++ b/csflow/Cargo.toml @@ -9,8 +9,8 @@ description = "SDK for CS2 cheats utilizing memflow" [dependencies] # memory -memflow = "0.2.0" dataview = "1.0.1" +memflow = { git = "https://github.com/memflow/memflow.git", rev = "c19d870" } # logging log = "0.4.19" diff --git a/csflow/src/context/cached_view.rs b/csflow/src/context/cached_view.rs new file mode 100644 index 0000000..4439be1 --- /dev/null +++ b/csflow/src/context/cached_view.rs @@ -0,0 +1,122 @@ +use ::std::sync::{atomic::{AtomicU8, Ordering, AtomicI32}, Arc}; + +use memflow::prelude::v1::*; + +pub const INVALIDATE_ALWAYS: u8 = 0b00000001; +pub const INVALIDATE_TICK: u8 = 0b00000010; + +pub struct ExternallyControlledValidator { + validator_next_flags: Arc<AtomicU8>, + validator_tick_count: Arc<AtomicI32>, +} + +impl ExternallyControlledValidator { + pub fn new() -> Self { + Self { + validator_next_flags: Arc::new(AtomicU8::new(INVALIDATE_ALWAYS)), + validator_tick_count: Arc::new(AtomicI32::new(0)), + } + } + + pub fn set_next_flags(&mut self, flags: u8) { + self.validator_next_flags + .store(flags as u8, Ordering::SeqCst); + } + + pub fn set_tick_count(&mut self, tick_count: i32) { + self.validator_tick_count + .store(tick_count, Ordering::SeqCst); + } + + pub fn validator(&self) -> CustomValidator { + CustomValidator::new( + self.validator_next_flags.clone(), + self.validator_tick_count.clone(), + ) + } +} + +#[derive(Clone)] +struct ValidatorSlot { + value: i32, + flags: u8, +} + +#[derive(Clone)] +pub struct CustomValidator { + slots: Vec<ValidatorSlot>, + + // The invalidation flags used for the next read or write. + next_flags: Arc<AtomicU8>, + next_flags_local: u8, + + // last_count is used to quickly invalidate slots without having to + // iterate over all slots and invalidating manually. + last_count: usize, + + // tick count is the externally controlled tick number that will + // invalidate specific caches when it is increased. + tick_count: Arc<AtomicI32>, + tick_count_local: i32, +} + +impl CustomValidator { + pub fn new(next_flags: Arc<AtomicU8>, tick_count: Arc<AtomicI32>) -> Self { + Self { + slots: vec![], + next_flags, + next_flags_local: INVALIDATE_ALWAYS, + last_count: 0, + tick_count, + tick_count_local: -1, + } + } +} + +impl CacheValidator for CustomValidator { + // Create a vector containing all slots with a predefined invalid state. + fn allocate_slots(&mut self, slot_count: usize) { + self.slots.resize( + slot_count, + ValidatorSlot { + value: -1, + flags: INVALIDATE_ALWAYS, + }, + ); + } + + // This function is invoked on every batch of memory operations. + // This simply updates the internal state and reads the Atomic variables for the upcoming validations. + fn update_validity(&mut self) { + self.last_count = self.last_count.wrapping_add(1); + self.next_flags_local = self.next_flags.load(Ordering::SeqCst); + self.tick_count_local = self.tick_count.load(Ordering::SeqCst); + } + + // This simply returns true or false if the slot is valid or not. + // `last_count` is used here to invalidate slots quickly without requiring to iterate over the entire slot list. + fn is_slot_valid(&self, slot_id: usize) -> bool { + match self.slots[slot_id].flags { + INVALIDATE_ALWAYS => self.slots[slot_id].value == self.last_count as i32, + INVALIDATE_TICK => self.slots[slot_id].value == self.tick_count_local as i32, + _ => false, + } + } + + // In case the cache is being updates this function marks the slot as being valid. + fn validate_slot(&mut self, slot_id: usize) { + match self.next_flags_local { + INVALIDATE_ALWAYS => self.slots[slot_id].value = self.last_count as i32, + INVALIDATE_TICK => self.slots[slot_id].value = self.tick_count_local as i32, + _ => (), + } + + self.slots[slot_id].flags = self.next_flags_local; + } + + // In case a slot has to be freed this function resets it to the default values. + fn invalidate_slot(&mut self, slot_id: usize) { + self.slots[slot_id].value = -1; + self.slots[slot_id].flags = INVALIDATE_ALWAYS; + } +} \ No newline at end of file diff --git a/csflow/src/context.rs b/csflow/src/context/mod.rs similarity index 66% rename from csflow/src/context.rs rename to csflow/src/context/mod.rs index 49731fb..d4aa09e 100644 --- a/csflow/src/context.rs +++ b/csflow/src/context/mod.rs @@ -1,16 +1,23 @@ -use memflow::{plugins::{IntoProcessInstanceArcBox, Inventory, ConnectorArgs, args::Args}, os::{ModuleInfo, Os, Process}, mem::MemoryView, types::Address}; +use memflow::{plugins::{IntoProcessInstanceArcBox, Inventory, ConnectorArgs, args::Args}, os::{ModuleInfo, Os, Process}, mem::{MemoryView, CachedView}, types::Address}; +use memflow::prelude::v1::size; use crate::{error::Error, structs::{CPlayerController, CBaseEntity, GlobalVars, GameRules}, cs2dumper, traits::MemoryClass}; +use self::cached_view::{ExternallyControlledValidator, CustomValidator, INVALIDATE_TICK}; + +pub mod cached_view; + pub struct CheatCtx { pub process: IntoProcessInstanceArcBox<'static>, + pub memory: CachedView<'static, IntoProcessInstanceArcBox<'static>, CustomValidator>, + pub cache_controller: ExternallyControlledValidator, pub client_module: ModuleInfo, pub engine_module: ModuleInfo, } impl CheatCtx { fn check_version(&mut self) -> Result<(), Error> { - let game_build_number: u32 = self.process.read(self.engine_module.base + cs2dumper::offsets::engine2_dll::dwBuildNumber)?; + let game_build_number: u32 = self.memory.read(self.engine_module.base + cs2dumper::offsets::engine2_dll::dwBuildNumber)?; let offset_build_number = cs2dumper::offsets::game_info::buildNumber; if game_build_number as usize != offset_build_number { @@ -49,8 +56,24 @@ impl CheatCtx { let engine_module = process.module_by_name("engine2.dll")?; + // Create the validator + let mut validator_controller = ExternallyControlledValidator::new(); + let validator = validator_controller.validator(); + + // Create CachedView over the processes MemoryView + let proc_arch = process.info().proc_arch; + let cached_process = CachedView::builder(process.clone()) + .arch(proc_arch) + .validator(validator) + .cache_size(size::mb(10)) + .build()?; + + validator_controller.set_next_flags(INVALIDATE_TICK); + let mut ctx = Self { - process, + process: process, + memory: cached_process, + cache_controller: validator_controller, client_module, engine_module, }; @@ -61,29 +84,29 @@ impl CheatCtx { } pub fn get_local(&mut self) -> Result<CPlayerController, Error> { - let ptr = self.process.read_addr64(self.client_module.base + cs2dumper::offsets::client_dll::dwLocalPlayerController)?; + let ptr = self.memory.read_addr64(self.client_module.base + cs2dumper::offsets::client_dll::dwLocalPlayerController)?; Ok(CPlayerController::new(ptr)) } - + pub fn get_plantedc4(&mut self) -> Result<CBaseEntity, Error> { - let ptr = self.process.read_addr64(self.client_module.base + cs2dumper::offsets::client_dll::dwPlantedC4)?; - let ptr2 = self.process.read_addr64(ptr)?; + let ptr = self.memory.read_addr64(self.client_module.base + cs2dumper::offsets::client_dll::dwPlantedC4)?; + let ptr2 = self.memory.read_addr64(ptr)?; Ok(CBaseEntity::new(ptr2)) } pub fn get_globals(&mut self) -> Result<GlobalVars, Error> { - let ptr = self.process.read_addr64(self.client_module.base + cs2dumper::offsets::client_dll::dwGlobalVars)?; + let ptr = self.memory.read_addr64(self.client_module.base + cs2dumper::offsets::client_dll::dwGlobalVars)?; Ok(GlobalVars::new(ptr)) } pub fn get_gamerules(&mut self) -> Result<GameRules, Error> { - let ptr = self.process.read_addr64(self.client_module.base + cs2dumper::offsets::client_dll::dwGameRules)?; + let ptr = self.memory.read_addr64(self.client_module.base + cs2dumper::offsets::client_dll::dwGameRules)?; Ok(GameRules::new(ptr)) } // todo: separate into own class pub fn get_entity_list(&mut self) -> Result<Address, Error> { - let ptr = self.process.read_addr64(self.client_module.base + cs2dumper::offsets::client_dll::dwEntityList)?; + let ptr = self.memory.read_addr64(self.client_module.base + cs2dumper::offsets::client_dll::dwEntityList)?; Ok(ptr) } diff --git a/csflow/src/error.rs b/csflow/src/error.rs index cade3e7..f456339 100644 --- a/csflow/src/error.rs +++ b/csflow/src/error.rs @@ -32,5 +32,8 @@ pub enum Error { MemflowPartialf32(#[from] memflow::error::PartialError<f32>), #[error("memflow partial error when reading u8: {0}")] - MemflowPartialu8(#[from] memflow::error::PartialError<u8>) + MemflowPartialu8(#[from] memflow::error::PartialError<u8>), + + #[error("memflow partial error when reading Vec<u8>: {0}")] + MemflowPartialVecu8(#[from] memflow::error::PartialError<Vec<u8>>) } \ No newline at end of file diff --git a/csflow/src/structs/entity/base_entity.rs b/csflow/src/structs/entity/base_entity.rs index 6a6ea3d..ce6ce55 100644 --- a/csflow/src/structs/entity/base_entity.rs +++ b/csflow/src/structs/entity/base_entity.rs @@ -16,12 +16,12 @@ impl MemoryClass for CBaseEntity { impl BaseEntity for CBaseEntity { fn from_index(ctx: &mut CheatCtx, entity_list: Address, index: i32) -> Result<Option<CBaseEntity>, Error> { - let list_entry = ctx.process.read_addr64(entity_list + 8 * (index >> 9) + 16)?; + let list_entry = ctx.memory.read_addr64(entity_list + 8 * (index >> 9) + 16)?; if list_entry.is_null() && !list_entry.is_valid() { return Ok(None); } - let player_ptr = ctx.process.read_addr64(list_entry + 120 * (index & 0x1FF))?; + let player_ptr = ctx.memory.read_addr64(list_entry + 120 * (index & 0x1FF))?; if player_ptr.is_null() && !player_ptr.is_valid() { return Ok(None); } @@ -30,14 +30,28 @@ impl BaseEntity for CBaseEntity { } fn pos(&self, ctx: &mut CheatCtx) -> Result<Vec3, Error> { - let node = ctx.process.read_addr64(self.0 + cs2dumper::client::C_BaseEntity::m_pGameSceneNode)?; - Ok(ctx.process.read(node + cs2dumper::client::CGameSceneNode::m_vecAbsOrigin)?) + let node = ctx.memory.read_addr64(self.0 + cs2dumper::client::C_BaseEntity::m_pGameSceneNode)?; + Ok(ctx.memory.read(node + cs2dumper::client::CGameSceneNode::m_vecAbsOrigin)?) } fn class_name(&self, ctx: &mut CheatCtx) -> Result<String, Error> { let entity_identity_ptr = ctx.process.read_addr64(self.0 + cs2dumper::client::CEntityInstance::m_pEntity)?; let class_name_ptr = ctx.process.read_addr64(entity_identity_ptr + cs2dumper::client::CEntityIdentity::m_designerName)?; - Ok(ctx.process.read_char_string_n(class_name_ptr, 32)?) + Ok(ctx.memory.read_char_string_n(class_name_ptr, 32)?) + } + + fn next_by_class(&self, ctx: &mut CheatCtx) -> Result<Self, Error> where Self: std::marker::Sized { + let entity_identity_ptr = ctx.process.read_addr64(self.0 + cs2dumper::client::CEntityInstance::m_pEntity)?; + let next_by_class_ptr = ctx.process.read_addr64(entity_identity_ptr + cs2dumper::client::CEntityIdentity::m_pNextByClass)?; + let entity_ptr = ctx.process.read_addr64(next_by_class_ptr)?; + Ok(Self(entity_ptr)) + } + + fn previous_by_class(&self, ctx: &mut CheatCtx) -> Result<Self, Error> where Self: std::marker::Sized { + let entity_identity_ptr = ctx.memory.read_addr64(self.0 + cs2dumper::client::CEntityInstance::m_pEntity)?; + let next_by_class_ptr = ctx.process.read_addr64(entity_identity_ptr + cs2dumper::client::CEntityIdentity::m_pPrevByClass)?; + let entity_ptr = ctx.process.read_addr64(next_by_class_ptr)?; + Ok(Self(entity_ptr)) } } @@ -45,4 +59,24 @@ impl CBaseEntity { pub fn to_player_controller(&self) -> super::CPlayerController { super::CPlayerController::new(self.0) } + + /// Professionally engineered function to quickly check if the entity has class name "weapon_c4" + pub fn is_dropped_c4(&self, ctx: &mut CheatCtx) -> Result<bool, Error> { + let entity_identity_ptr = ctx.process.read_addr64(self.0 + cs2dumper::client::CEntityInstance::m_pEntity)?; + let class_name_ptr = ctx.process.read_addr64(entity_identity_ptr + cs2dumper::client::CEntityIdentity::m_designerName)?; + + let data = ctx.process.read_raw(class_name_ptr + 7, 2)?; + let is_c4 = data == "c4".as_bytes(); + Ok(is_c4) + } + + /// Professionally engineered function to quickly check if the entity has class name "cs_player_controller" + pub fn is_cs_player_controller(&self, ctx: &mut CheatCtx) -> Result<bool, Error> { + let entity_identity_ptr = ctx.process.read_addr64(self.0 + cs2dumper::client::CEntityInstance::m_pEntity)?; + let class_name_ptr = ctx.process.read_addr64(entity_identity_ptr + cs2dumper::client::CEntityIdentity::m_designerName)?; + + let data = ctx.process.read_raw(class_name_ptr, 20)?; + let is_controller = data == "cs_player_controller".as_bytes(); + Ok(is_controller) + } } \ No newline at end of file diff --git a/csflow/src/structs/entity/player_controller.rs b/csflow/src/structs/entity/player_controller.rs index 6a2cd07..33668f2 100644 --- a/csflow/src/structs/entity/player_controller.rs +++ b/csflow/src/structs/entity/player_controller.rs @@ -18,12 +18,12 @@ impl MemoryClass for CPlayerController { impl BaseEntity for CPlayerController { fn from_index(ctx: &mut CheatCtx, entity_list: Address, index: i32) -> Result<Option<CPlayerController>, Error> { - let list_entry = ctx.process.read_addr64(entity_list + 8 * (index >> 9) + 16)?; + let list_entry = ctx.memory.read_addr64(entity_list + 8 * (index >> 9) + 16)?; if list_entry.is_null() && !list_entry.is_valid() { return Ok(None); } - let player_ptr = ctx.process.read_addr64(list_entry + 120 * (index & 0x1FF))?; + let player_ptr = ctx.memory.read_addr64(list_entry + 120 * (index & 0x1FF))?; if player_ptr.is_null() && !player_ptr.is_valid() { return Ok(None); } @@ -31,27 +31,52 @@ impl BaseEntity for CPlayerController { Ok(Some(Self::new(player_ptr))) } + /// This function is slower because of an additional pointer read, use pawn.pos() instead. fn pos(&self, ctx: &mut CheatCtx) -> Result<Vec3, Error> { - let node = ctx.process.read_addr64(self.0 + cs2dumper::client::C_BaseEntity::m_pGameSceneNode)?; - Ok(ctx.process.read(node + cs2dumper::client::CGameSceneNode::m_vecAbsOrigin)?) + let node = ctx.memory.read_addr64(self.0 + cs2dumper::client::C_BaseEntity::m_pGameSceneNode)?; + Ok(ctx.memory.read(node + cs2dumper::client::CGameSceneNode::m_vecAbsOrigin)?) } fn class_name(&self, ctx: &mut CheatCtx) -> Result<String, Error> { let entity_identity_ptr = ctx.process.read_addr64(self.0 + cs2dumper::client::CEntityInstance::m_pEntity)?; let class_name_ptr = ctx.process.read_addr64(entity_identity_ptr + cs2dumper::client::CEntityIdentity::m_designerName)?; - Ok(ctx.process.read_char_string_n(class_name_ptr, 32)?) + Ok(ctx.memory.read_char_string_n(class_name_ptr, 32)?) + } + + fn next_by_class(&self, ctx: &mut CheatCtx) -> Result<Self, Error> where Self: std::marker::Sized { + let entity_identity_ptr = ctx.process.read_addr64(self.0 + cs2dumper::client::CEntityInstance::m_pEntity)?; + let next_by_class_ptr = ctx.process.read_addr64(entity_identity_ptr + cs2dumper::client::CEntityIdentity::m_pNextByClass)?; + + let class_name_ptr = ctx.process.read_addr64(entity_identity_ptr + cs2dumper::client::CEntityIdentity::m_designerName)?; + let class_name= ctx.process.read_char_string_n(class_name_ptr, 32)?; + + println!("class_name: {}", class_name); + + let stringable_index: i32 = ctx.process.read(entity_identity_ptr + cs2dumper::client::CEntityIdentity::m_nameStringableIndex)?; + println!("stringable_index: {}", stringable_index); + + let entity_ptr = ctx.process.read_addr64(next_by_class_ptr)?; + println!("entity_ptr: {}", entity_ptr); + Ok(Self(entity_ptr)) + } + + fn previous_by_class(&self, ctx: &mut CheatCtx) -> Result<Self, Error> where Self: std::marker::Sized { + let entity_identity_ptr = ctx.process.read_addr64(self.0 + cs2dumper::client::CEntityInstance::m_pEntity)?; + let next_by_class_ptr = ctx.process.read_addr64(entity_identity_ptr + cs2dumper::client::CEntityIdentity::m_pPrevByClass)?; + let entity_ptr = ctx.process.read_addr64(next_by_class_ptr)?; + Ok(Self(entity_ptr)) } } impl CPlayerController { pub fn get_pawn(&self, ctx: &mut CheatCtx, entity_list: Address) -> Result<Option<super::CPlayerPawn>, Error> { - let uhandle = ctx.process.read(self.0 + cs2dumper::client::CCSPlayerController::m_hPlayerPawn)?; + let uhandle = ctx.memory.read(self.0 + cs2dumper::client::CCSPlayerController::m_hPlayerPawn)?; super::CPlayerPawn::from_uhandle(ctx, entity_list, uhandle) } // Technically something that should be in the BaseEntity trait, but we are only gonna use it on CPlayerController pub fn get_team(&self, ctx: &mut CheatCtx) -> Result<Option<TeamID>, Error> { - let team_num: i32 = ctx.process.read(self.0 + cs2dumper::client::C_BaseEntity::m_iTeamNum)?; + let team_num: i32 = ctx.memory.read(self.0 + cs2dumper::client::C_BaseEntity::m_iTeamNum)?; Ok(TeamID::from_i32(team_num)) } diff --git a/csflow/src/structs/entity/player_pawn.rs b/csflow/src/structs/entity/player_pawn.rs index 482efee..08cd8ec 100644 --- a/csflow/src/structs/entity/player_pawn.rs +++ b/csflow/src/structs/entity/player_pawn.rs @@ -17,12 +17,12 @@ impl MemoryClass for CPlayerPawn { impl CPlayerPawn { pub fn from_uhandle(ctx: &mut CheatCtx, entity_list: Address, uhandle: u32) -> Result<Option<Self>, Error> { - let list_entry = ctx.process.read_addr64(entity_list + 0x8 * ((uhandle & 0x7FFF) >> 9) + 16)?; + let list_entry = ctx.memory.read_addr64(entity_list + 0x8 * ((uhandle & 0x7FFF) >> 9) + 16)?; if list_entry.is_null() || !list_entry.is_valid() { Ok(None) } else { - let ptr = ctx.process.read_addr64(list_entry + 120 * (uhandle & 0x1FF))?; + let ptr = ctx.memory.read_addr64(list_entry + 120 * (uhandle & 0x1FF))?; Ok(Some(Self(ptr))) } } @@ -30,27 +30,27 @@ impl CPlayerPawn { // Todo: Optimize this function: find another way to do this pub fn has_c4(&self, ctx: &mut CheatCtx, entity_list: Address) -> Result<bool, Error> { let mut has_c4 = false; - let wep_services = ctx.process.read_addr64(self.0 + cs2dumper::client::C_BasePlayerPawn::m_pWeaponServices)?; - let wep_count: i32 = ctx.process.read(wep_services + cs2dumper::client::CPlayer_WeaponServices::m_hMyWeapons)?; - let wep_base = ctx.process.read_addr64(wep_services + cs2dumper::client::CPlayer_WeaponServices::m_hMyWeapons + 0x8)?; + let wep_services = ctx.memory.read_addr64(self.0 + cs2dumper::client::C_BasePlayerPawn::m_pWeaponServices)?; + let wep_count: i32 = ctx.memory.read(wep_services + cs2dumper::client::CPlayer_WeaponServices::m_hMyWeapons)?; + let wep_base = ctx.memory.read_addr64(wep_services + cs2dumper::client::CPlayer_WeaponServices::m_hMyWeapons + 0x8)?; for wep_idx in 0..wep_count { - let handle: i32 = ctx.process.read(wep_base + wep_idx * 0x4)?; + let handle: i32 = ctx.memory.read(wep_base + wep_idx * 0x4)?; if handle == -1 { continue; } - let list_entry = ctx.process.read_addr64(entity_list + 0x8 * ((handle & 0x7FFF) >> 9) + 16)?; + let list_entry = ctx.memory.read_addr64(entity_list + 0x8 * ((handle & 0x7FFF) >> 9) + 16)?; if let Some(wep_ptr) = { if list_entry.is_null() || !list_entry.is_valid() { None } else { - let ptr = ctx.process.read_addr64(list_entry + 120 * (handle & 0x1FF))?; + let ptr = ctx.memory.read_addr64(list_entry + 120 * (handle & 0x1FF))?; Some(ptr) } } { - let wep_data = ctx.process.read_addr64(wep_ptr + cs2dumper::client::C_BaseEntity::m_nSubclassID + 0x8)?; - let id: i32 = ctx.process.read(wep_data + cs2dumper::client::CCSWeaponBaseVData::m_WeaponType)?; + let wep_data = ctx.memory.read_addr64(wep_ptr + cs2dumper::client::C_BaseEntity::m_nSubclassID + 0x8)?; + let id: i32 = ctx.memory.read(wep_data + cs2dumper::client::CCSWeaponBaseVData::m_WeaponType)?; if id == 7 { has_c4 = true; @@ -63,15 +63,15 @@ impl CPlayerPawn { } pub fn pos(&self, ctx: &mut CheatCtx) -> Result<Vec3, Error> { - Ok(ctx.process.read(self.0 + cs2dumper::client::C_BasePlayerPawn::m_vOldOrigin)?) + Ok(ctx.memory.read(self.0 + cs2dumper::client::C_BasePlayerPawn::m_vOldOrigin)?) } pub fn angles(&self, ctx: &mut CheatCtx) -> Result<Vec3, Error> { - Ok(ctx.process.read(self.0 + cs2dumper::client::C_CSPlayerPawnBase::m_angEyeAngles)?) + Ok(ctx.memory.read(self.0 + cs2dumper::client::C_CSPlayerPawnBase::m_angEyeAngles)?) } pub fn health(&self, ctx: &mut CheatCtx) -> Result<u32, Error> { - Ok(ctx.process.read(self.0 + cs2dumper::client::C_BaseEntity::m_iHealth)?) + Ok(ctx.memory.read(self.0 + cs2dumper::client::C_BaseEntity::m_iHealth)?) } /// Same as ::get_health > 0 diff --git a/csflow/src/structs/gamerules.rs b/csflow/src/structs/gamerules.rs index 87550c2..67fc338 100644 --- a/csflow/src/structs/gamerules.rs +++ b/csflow/src/structs/gamerules.rs @@ -16,20 +16,20 @@ impl MemoryClass for GameRules { impl GameRules { pub fn bomb_dropped(&self, ctx: &mut CheatCtx) -> Result<bool, Error> { - let data: u8 = ctx.process.read(self.0 + cs2dumper::client::C_CSGameRules::m_bBombDropped)?; + let data: u8 = ctx.memory.read(self.0 + cs2dumper::client::C_CSGameRules::m_bBombDropped)?; Ok(data != 0) } pub fn total_rounds_played(&self, ctx: &mut CheatCtx) -> Result<i32, Error> { - Ok(ctx.process.read(self.0 + cs2dumper::client::C_CSGameRules::m_totalRoundsPlayed)?) + Ok(ctx.memory.read(self.0 + cs2dumper::client::C_CSGameRules::m_totalRoundsPlayed)?) } pub fn game_phase(&self, ctx: &mut CheatCtx) -> Result<i32, Error> { - Ok(ctx.process.read(self.0 + cs2dumper::client::C_CSGameRules::m_gamePhase)?) + Ok(ctx.memory.read(self.0 + cs2dumper::client::C_CSGameRules::m_gamePhase)?) } pub fn bomb_planted(&self, ctx: &mut CheatCtx) -> Result<bool, Error> { - let data: u8 = ctx.process.read(self.0 + cs2dumper::client::C_CSGameRules::m_bBombPlanted)?; + let data: u8 = ctx.memory.read(self.0 + cs2dumper::client::C_CSGameRules::m_bBombPlanted)?; Ok(data != 0) } } \ No newline at end of file diff --git a/csflow/src/structs/global_vars.rs b/csflow/src/structs/global_vars.rs index 3f8940e..4895026 100644 --- a/csflow/src/structs/global_vars.rs +++ b/csflow/src/structs/global_vars.rs @@ -1,5 +1,5 @@ use memflow::{types::Address, mem::MemoryView}; -use crate::{traits::MemoryClass, CheatCtx, Error}; +use crate::{traits::MemoryClass, CheatCtx, Error, cached_view::INVALIDATE_ALWAYS}; #[derive(Debug, Clone, Copy)] pub struct GlobalVars(Address); @@ -16,6 +16,7 @@ impl MemoryClass for GlobalVars { impl GlobalVars { pub fn real_time(&self, ctx: &mut CheatCtx) -> Result<f32, Error> { + ctx.cache_controller.set_next_flags(INVALIDATE_ALWAYS); Ok(ctx.process.read(self.0)?) } @@ -40,7 +41,7 @@ impl GlobalVars { } pub fn map_name(&self, ctx: &mut CheatCtx) -> Result<String, Error> { - let ptr = ctx.process.read_addr64(self.0 + 0x188)?; + let ptr = ctx.memory.read_addr64(self.0 + 0x188)?; Ok(ctx.process.read_char_string_n(ptr, 32)?) } } diff --git a/csflow/src/traits/base_entity.rs b/csflow/src/traits/base_entity.rs index f610466..8c401e4 100644 --- a/csflow/src/traits/base_entity.rs +++ b/csflow/src/traits/base_entity.rs @@ -8,4 +8,6 @@ pub trait BaseEntity { fn from_index(ctx: &mut CheatCtx, entity_list: Address, index: i32) -> Result<Option<Self>, Error> where Self: std::marker::Sized; fn pos(&self, ctx: &mut CheatCtx) -> Result<Vec3, Error>; fn class_name(&self, ctx: &mut CheatCtx) -> Result<String, Error>; + fn next_by_class(&self, ctx: &mut CheatCtx) -> Result<Self, Error> where Self: std::marker::Sized; + fn previous_by_class(&self, ctx: &mut CheatCtx) -> Result<Self, Error> where Self: std::marker::Sized; } \ No newline at end of file diff --git a/radarflow/src/dma/cache.rs b/radarflow/src/dma/cache.rs index f99825d..8d4026e 100644 --- a/radarflow/src/dma/cache.rs +++ b/radarflow/src/dma/cache.rs @@ -1,4 +1,4 @@ -use csflow::{memflow::Address, enums::PlayerType, structs::{GlobalVars, GameRules}, traits::MemoryClass}; +use csflow::{memflow::Address, enums::PlayerType, structs::{GlobalVars, GameRules, CBaseEntity}, traits::{MemoryClass, BaseEntity}, CheatCtx}; #[derive(Clone, Copy)] pub enum CachedEntity { @@ -18,7 +18,7 @@ pub struct Cache { impl Cache { pub fn is_valid(&self) -> bool { if self.valid { - if self.timestamp.elapsed() > std::time::Duration::from_secs(10) { + if self.timestamp.elapsed() > std::time::Duration::from_secs(60 * 3) { log::info!("Invalidated cache! Reason: time"); return false } @@ -57,6 +57,107 @@ impl Cache { pub fn gamerules(&self) -> GameRules { self.gamerules } + + pub fn build(&self, ctx: &mut CheatCtx) -> anyhow::Result<Self> { + let mut cached_entities = Vec::new(); + + let globals = ctx.get_globals()?; + let highest_index = ctx.highest_entity_index()?; + let entity_list = ctx.get_entity_list()?; + let gamerules = ctx.get_gamerules()?; + + let local = ctx.get_local()?; + + if local.get_pawn(ctx, entity_list)?.is_some() { + + cached_entities.push(CachedEntity::Player { + ptr: local.ptr(), + player_type: PlayerType::Local, + }); + + // If the bomb is dropped, do a reverse entity list loop with early exit when we found the bomb. + if gamerules.bomb_dropped(ctx)? { + for idx in (0..=highest_index).rev() { + if let Some(entity) = CBaseEntity::from_index(ctx, entity_list, idx)? { + if entity.is_dropped_c4(ctx)? { + cached_entities.push(CachedEntity::Bomb { + ptr: entity.ptr() + }); + break; + } + } + } + } + + //let mut next = local.next_by_class(ctx)?; + //println!("next: {:X}", next.ptr()); + + for idx in 0..=64 { + if let Some(entity) = CBaseEntity::from_index(ctx, entity_list, idx)? { + if entity.is_cs_player_controller(ctx)? { + let controller = entity.to_player_controller(); + + let player_type = { + match controller.get_player_type(ctx, &local)? { + Some(t) => { + if t == PlayerType::Spectator { + continue; + } else { t } + }, + None => { + continue; + }, + } + }; + + cached_entities.push(CachedEntity::Player { + ptr: entity.ptr(), + player_type + }); + } + } + } + + + /* + while !next.ptr().is_null() && next.ptr().is_valid() { + let player_type = { + match next.get_player_type(ctx, &local)? { + Some(t) => { + if t == PlayerType::Spectator { + next = next.next_by_class(ctx)?; + continue; + } else { t } + }, + None => { + next = next.next_by_class(ctx)?; + continue; + }, + } + }; + + cached_entities.push(CachedEntity::Player { + ptr: next.ptr(), + player_type, + }); + + next = next.next_by_class(ctx)?; + println!("next: {:X}", next.ptr()); + } + */ + } + + let cache = CacheBuilder::new() + .entity_cache(cached_entities) + .entity_list(entity_list) + .globals(globals) + .gamerules(gamerules) + .build()?; + + log::info!("Rebuilt cache."); + + Ok(cache) + } } pub struct CacheBuilder { diff --git a/radarflow/src/dma/mod.rs b/radarflow/src/dma/mod.rs index 364cae8..eb23599 100644 --- a/radarflow/src/dma/mod.rs +++ b/radarflow/src/dma/mod.rs @@ -1,9 +1,9 @@ use std::sync::Arc; -use csflow::{CheatCtx, Connector, memflow::Process, traits::{MemoryClass, BaseEntity}, enums::PlayerType, structs::{CBaseEntity, CPlayerController}}; +use csflow::{CheatCtx, Connector, memflow::Process, traits::{MemoryClass, BaseEntity}, structs::{CBaseEntity, CPlayerController}, enums::{PlayerType, TeamID}}; use tokio::{sync::RwLock, time::Duration}; -use crate::{comms::{RadarData, EntityData, BombData, PlayerData}, dma::cache::CacheBuilder}; +use crate::comms::{RadarData, EntityData, BombData, PlayerData}; use self::cache::Cache; @@ -32,66 +32,20 @@ pub async fn run(connector: Connector, pcileech_device: String, data_lock: Arc<R } if !cache.is_valid() { - let mut cached_entities = Vec::new(); - - let globals = ctx.get_globals()?; - let highest_index = ctx.highest_entity_index()?; - let entity_list = ctx.get_entity_list()?; - let gamerules = ctx.get_gamerules()?; - - let local = ctx.get_local()?; - - if local.get_pawn(&mut ctx, entity_list)?.is_some() { - cached_entities.push(cache::CachedEntity::Player { - ptr: local.ptr(), - player_type: PlayerType::Local - }); - - for idx in 1..=highest_index { - if let Some(entity) = CBaseEntity::from_index(&mut ctx, entity_list, idx)? { - - let class_name = entity.class_name(&mut ctx)?; - - match class_name.as_str() { - "weapon_c4" => { - cached_entities.push(cache::CachedEntity::Bomb { - ptr: entity.ptr() - }) - }, - "cs_player_controller" => { - let controller = entity.to_player_controller(); - - let player_type = { - match controller.get_player_type(&mut ctx, &local)? { - Some(t) => { - if t == PlayerType::Spectator { continue } else { t } - }, - None => { continue }, - } - }; - - cached_entities.push(cache::CachedEntity::Player { - ptr: entity.ptr(), - player_type, - }) - } - _ => {} - } - } - } - } - - cache = CacheBuilder::new() - .entity_cache(cached_entities) - .entity_list(entity_list) - .globals(globals) - .gamerules(gamerules) - .build()?; - - log::info!("Rebuilt cache."); + log::debug!("Rebuilding cache!"); + let cache_start_stamp = tokio::time::Instant::now(); + cache = cache.build(&mut ctx)?; + let elapsed = cache_start_stamp.elapsed(); + log::info!("cache rebuild rate: {:.2}Hz", SECOND_AS_NANO as f64 / start_stamp.elapsed().as_nanos() as f64); + log::info!("cache rebuilt took: {}ns", elapsed.as_nanos()); + log::info!("loop target: {}ns", target_interval.as_nanos()); } if ctx.network_is_ingame()? { + let cur_tickcount = cache.globals().tick_count(&mut ctx)?; + //println!("tick_count: {}", tick_count); + ctx.cache_controller.set_tick_count(cur_tickcount); + // Check if mapname is "<empty>" // That means we are not in-game, so we can just write empty radar data and run the next loop. let map_name = cache.globals().map_name(&mut ctx)?; @@ -102,11 +56,9 @@ pub async fn run(connector: Connector, pcileech_device: String, data_lock: Arc<R let mut data = data_lock.write().await; *data = RadarData::empty(); - continue; } else if map_name.is_empty() { // Check if mapname is empty, this usually means a bad globals pointer -> rebuild our cache cache.invalidate(); log::info!("Invalidated cache! Reason: invalid globals pointer"); - continue; } let cur_round = cache.gamerules().total_rounds_played(&mut ctx)?; @@ -116,7 +68,6 @@ pub async fn run(connector: Connector, pcileech_device: String, data_lock: Arc<R last_round = cur_round; cache.invalidate(); log::info!("Invalidated cache! Reason: new round"); - continue; } let cur_gamephase = cache.gamerules().game_phase(&mut ctx)?; @@ -126,7 +77,6 @@ pub async fn run(connector: Connector, pcileech_device: String, data_lock: Arc<R last_gamephase = cur_gamephase; cache.invalidate(); log::info!("Invalidated cache! Reason: new gamephase"); - continue; } let cur_bomb_dropped = cache.gamerules().bomb_dropped(&mut ctx)?; @@ -135,12 +85,8 @@ pub async fn run(connector: Connector, pcileech_device: String, data_lock: Arc<R last_bomb_dropped = cur_bomb_dropped; cache.invalidate(); log::info!("Invalidated cache! Reason: bomb drop status changed"); - continue; } - let cur_tickcount = cache.globals().tick_count(&mut ctx)?; - - // New tick, now we want to fetch our data if cur_tickcount != last_tickcount { // We don't expect more than 16 entries in our radar data. let mut radar_data = Vec::with_capacity(16); @@ -155,7 +101,7 @@ pub async fn run(connector: Connector, pcileech_device: String, data_lock: Arc<R )) ); } - + for cached_data in cache.entity_cache() { match cached_data { cache::CachedEntity::Bomb { ptr } => { @@ -179,8 +125,13 @@ pub async fn run(connector: Connector, pcileech_device: String, data_lock: Arc<R if pawn.is_alive(&mut ctx)? { let pos = pawn.pos(&mut ctx)?; let yaw = pawn.angles(&mut ctx)?.y; - let has_bomb = pawn.has_c4(&mut ctx, cache.entity_list())?; - + + let has_bomb = { + if controller.get_team(&mut ctx)? == Some(TeamID::T) { + pawn.has_c4(&mut ctx, cache.entity_list())? + } else {false} + }; + radar_data.push( EntityData::Player( PlayerData::new( @@ -197,14 +148,14 @@ pub async fn run(connector: Connector, pcileech_device: String, data_lock: Arc<R } } + last_tickcount = cur_tickcount; + let mut data = data_lock.write().await; *data = RadarData::new( true, map_name, radar_data ); - - last_tickcount = cur_tickcount; } } @@ -215,16 +166,18 @@ pub async fn run(connector: Connector, pcileech_device: String, data_lock: Arc<R // This gives us the remaining time we can sleep in our loop Some(t) => t, // No time left, start next loop. - None => continue + None => { Duration::ZERO } }; - // On linux we may use tokio_timerfd for a more finely grained sleep function - #[cfg(target_os = "linux")] - tokio_timerfd::sleep(remaining).await?; - - // On non linux build targets we need to use the regular sleep function, this one is only accurate to millisecond precision - #[cfg(not(target_os = "linux"))] - tokio::time::sleep(remaining).await; + if !remaining.is_zero() || true { + // On linux we may use tokio_timerfd for a more finely grained sleep function + #[cfg(target_os = "linux")] + tokio_timerfd::sleep(remaining).await?; + + // On non linux build targets we need to use the regular sleep function, this one is only accurate to millisecond precision + #[cfg(not(target_os = "linux"))] + tokio::time::sleep(remaining).await; + } log::debug!("poll rate: {:.2}Hz", SECOND_AS_NANO as f64 / start_stamp.elapsed().as_nanos() as f64); log::debug!("elapsed: {}ns", elapsed.as_nanos());