optimize (not done yet)
- alpha-alpha implementation of CachedView in csflow.
- MUCH faster entity cache build time thanks to two professionally engineered functions 😎
This commit is contained in:
parent
50677fafef
commit
f186b19255
26
.vscode/launch.json
vendored
Normal file
26
.vscode/launch.json
vendored
Normal file
@ -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}"
|
||||
},
|
||||
]
|
||||
}
|
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -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",
|
||||
|
@ -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"
|
||||
|
122
csflow/src/context/cached_view.rs
Normal file
122
csflow/src/context/cached_view.rs
Normal file
@ -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;
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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>>)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)?)
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -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 {
|
||||
|
@ -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());
|
||||
|
Loading…
x
Reference in New Issue
Block a user