From 7c652cb9843c96c045b2f4c5f98e325829020db0 Mon Sep 17 00:00:00 2001 From: Janek <development@superyu.xyz> Date: Sun, 31 Dec 2023 04:32:12 +0100 Subject: [PATCH] Update csflow: - Create structs for gamerules and global vars radarflow: - new dma loop with less frequent cache invalidation - The new loop tries to run at a fixed 128 hz. Thats the max tickrate in cs2. The data is also only updated when a tick change is detected, so that should keep data fetching to a minimum. - todo: more testing for cache invalidation --- Cargo.lock | 4 +- csflow/Cargo.toml | 2 +- csflow/src/context.rs | 33 +-- csflow/src/error.rs | 3 + csflow/src/structs/entity/base_entity.rs | 1 + .../src/structs/entity/player_controller.rs | 1 + csflow/src/structs/entity/player_pawn.rs | 1 + csflow/src/structs/gamerules.rs | 35 +++ csflow/src/structs/global_vars.rs | 67 +++++ csflow/src/structs/mod.rs | 4 + radarflow/Cargo.toml | 2 +- radarflow/src/cli.rs | 20 -- radarflow/src/dma/cache.rs | 62 +++-- radarflow/src/dma/mod.rs | 243 ++++++++++-------- radarflow/src/main.rs | 2 +- 15 files changed, 305 insertions(+), 175 deletions(-) create mode 100644 csflow/src/structs/gamerules.rs create mode 100644 csflow/src/structs/global_vars.rs diff --git a/Cargo.lock b/Cargo.lock index c312aa4..c0c773f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -477,7 +477,7 @@ dependencies = [ [[package]] name = "csflow" -version = "0.1.1" +version = "0.1.2" dependencies = [ "clap", "dataview 1.0.1", @@ -1474,7 +1474,7 @@ dependencies = [ [[package]] name = "radarflow" -version = "0.2.0" +version = "0.2.1" dependencies = [ "anyhow", "axum", diff --git a/csflow/Cargo.toml b/csflow/Cargo.toml index d7a9238..86255c1 100644 --- a/csflow/Cargo.toml +++ b/csflow/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "csflow" -version = "0.1.1" +version = "0.1.2" authors = ["Janek S <development@superyu.xyz>"] edition = "2021" description = "SDK for CS2 cheats utilizing memflow" diff --git a/csflow/src/context.rs b/csflow/src/context.rs index 137e741..0bb67db 100644 --- a/csflow/src/context.rs +++ b/csflow/src/context.rs @@ -1,6 +1,6 @@ use memflow::{plugins::{IntoProcessInstanceArcBox, Inventory, ConnectorArgs, args::Args}, os::{ModuleInfo, Os, Process}, mem::MemoryView, types::Address}; -use crate::{error::Error, structs::{CPlayerController, CBaseEntity}, cs2dumper, traits::MemoryClass}; +use crate::{error::Error, structs::{CPlayerController, CBaseEntity, GlobalVars, GameRules}, cs2dumper, traits::MemoryClass}; pub struct CheatCtx { pub process: IntoProcessInstanceArcBox<'static>, @@ -71,39 +71,30 @@ impl CheatCtx { Ok(CBaseEntity::new(ptr2)) } - pub fn is_bomb_planted(&mut self) -> Result<bool, Error> { - let game_rules = self.process.read_addr64(self.client_module.base + cs2dumper::offsets::client_dll::dwGameRules)?; - let data: u8 = self.process.read(game_rules + cs2dumper::client::C_CSGameRules::m_bBombPlanted)?; - Ok(data != 0) - } - - pub fn is_bomb_dropped(&mut self) -> Result<bool, Error> { - let game_rules = self.process.read_addr64(self.client_module.base + cs2dumper::offsets::client_dll::dwGameRules)?; - let data: u8 = self.process.read(game_rules + cs2dumper::client::C_CSGameRules::m_bBombDropped)?; - Ok(data != 0) + pub fn get_globals(&mut self) -> Result<GlobalVars, Error> { + let ptr = self.process.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)?; + Ok(GameRules::new(ptr)) + } + + // todo: seperate 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)?; Ok(ptr) } - pub fn get_globals(&mut self) -> Result<Address, Error> { - let ptr = self.process.read_addr64(self.client_module.base + cs2dumper::offsets::client_dll::dwGlobalVars)?; - Ok(ptr) - } - - pub fn map_name(&mut self, global_vars: Address) -> Result<String, Error> { - let ptr = self.process.read_addr64(global_vars + 0x188)?; - Ok(self.process.read_char_string_n(ptr, 32)?) - } - + // todo: seperate into own class pub fn highest_entity_index(&mut self) -> Result<i32, Error> { let game_entity_system = self.process.read_addr64(self.client_module.base + cs2dumper::offsets::client_dll::dwGameEntitySystem)?; let highest_index = self.process.read(game_entity_system + cs2dumper::offsets::client_dll::dwGameEntitySystem_getHighestEntityIndex)?; Ok(highest_index) } + // todo: seperate into own class pub fn network_is_ingame(&mut self) -> Result<bool, Error> { let ptr = self.process.read_addr64(self.engine_module.base + cs2dumper::offsets::engine2_dll::dwNetworkGameClient)?; let signonstate: i32 = self.process.read(ptr + cs2dumper::offsets::engine2_dll::dwNetworkGameClient_signOnState)?; diff --git a/csflow/src/error.rs b/csflow/src/error.rs index 86b2829..cade3e7 100644 --- a/csflow/src/error.rs +++ b/csflow/src/error.rs @@ -28,6 +28,9 @@ pub enum Error { #[error("memflow partial error when reading u32: {0}")] MemflowPartialu32(#[from] memflow::error::PartialError<u32>), + #[error("memflow partial error when reading f32: {0}")] + MemflowPartialf32(#[from] memflow::error::PartialError<f32>), + #[error("memflow partial error when reading u8: {0}")] MemflowPartialu8(#[from] memflow::error::PartialError<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 7abd191..6a6ea3d 100644 --- a/csflow/src/structs/entity/base_entity.rs +++ b/csflow/src/structs/entity/base_entity.rs @@ -1,6 +1,7 @@ use memflow::{types::Address, mem::MemoryView}; use crate::{CheatCtx, Error, cs2dumper, traits::{BaseEntity, MemoryClass}, structs::Vec3}; +#[derive(Debug, Clone, Copy)] pub struct CBaseEntity(Address); impl MemoryClass for CBaseEntity { diff --git a/csflow/src/structs/entity/player_controller.rs b/csflow/src/structs/entity/player_controller.rs index 8d6ee91..6a2cd07 100644 --- a/csflow/src/structs/entity/player_controller.rs +++ b/csflow/src/structs/entity/player_controller.rs @@ -3,6 +3,7 @@ use num_traits::FromPrimitive; use crate::{CheatCtx, Error, cs2dumper, structs::Vec3, traits::{MemoryClass, BaseEntity}, enums::{TeamID, PlayerType}}; +#[derive(Debug, Clone, Copy)] pub struct CPlayerController(Address); impl MemoryClass for CPlayerController { diff --git a/csflow/src/structs/entity/player_pawn.rs b/csflow/src/structs/entity/player_pawn.rs index ee9962f..482efee 100644 --- a/csflow/src/structs/entity/player_pawn.rs +++ b/csflow/src/structs/entity/player_pawn.rs @@ -2,6 +2,7 @@ use memflow::{types::Address, mem::MemoryView}; use crate::{Error, CheatCtx, cs2dumper, structs::Vec3, traits::MemoryClass}; +#[derive(Debug, Clone, Copy)] pub struct CPlayerPawn(Address); impl MemoryClass for CPlayerPawn { diff --git a/csflow/src/structs/gamerules.rs b/csflow/src/structs/gamerules.rs new file mode 100644 index 0000000..87550c2 --- /dev/null +++ b/csflow/src/structs/gamerules.rs @@ -0,0 +1,35 @@ +use memflow::{types::Address, mem::MemoryView}; +use crate::{traits::MemoryClass, CheatCtx, Error, cs2dumper}; + +#[derive(Debug, Clone, Copy)] +pub struct GameRules(Address); + +impl MemoryClass for GameRules { + fn ptr(&self) -> memflow::types::Address { + self.0 + } + + fn new(ptr: memflow::types::Address) -> Self { + Self(ptr) + } +} + +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)?; + 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)?) + } + + pub fn game_phase(&self, ctx: &mut CheatCtx) -> Result<i32, Error> { + Ok(ctx.process.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)?; + 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 new file mode 100644 index 0000000..3f8940e --- /dev/null +++ b/csflow/src/structs/global_vars.rs @@ -0,0 +1,67 @@ +use memflow::{types::Address, mem::MemoryView}; +use crate::{traits::MemoryClass, CheatCtx, Error}; + +#[derive(Debug, Clone, Copy)] +pub struct GlobalVars(Address); + +impl MemoryClass for GlobalVars { + fn ptr(&self) -> memflow::types::Address { + self.0 + } + + fn new(ptr: memflow::types::Address) -> Self { + Self(ptr) + } +} + +impl GlobalVars { + pub fn real_time(&self, ctx: &mut CheatCtx) -> Result<f32, Error> { + Ok(ctx.process.read(self.0)?) + } + + pub fn frame_count(&self, ctx: &mut CheatCtx) -> Result<i32, Error> { + Ok(ctx.process.read(self.0 + 0x4)?) + } + + pub fn frame_time(&self, ctx: &mut CheatCtx) -> Result<f32, Error> { + Ok(ctx.process.read(self.0 + 0x8)?) + } + + pub fn absolute_frame_time(&self, ctx: &mut CheatCtx) -> Result<f32, Error> { + Ok(ctx.process.read(self.0 + 0xC)?) + } + + pub fn max_clients(&self, ctx: &mut CheatCtx) -> Result<i32, Error> { + Ok(ctx.process.read(self.0 + 0x10)?) + } + + pub fn tick_count(&self, ctx: &mut CheatCtx) -> Result<i32, Error> { + Ok(ctx.process.read(self.0 + 0x40)?) + } + + pub fn map_name(&self, ctx: &mut CheatCtx) -> Result<String, Error> { + let ptr = ctx.process.read_addr64(self.0 + 0x188)?; + Ok(ctx.process.read_char_string_n(ptr, 32)?) + } +} + +/* +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 // NO fucking idea why the fuck this "should" be an f32???? + 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 +} +*/ \ No newline at end of file diff --git a/csflow/src/structs/mod.rs b/csflow/src/structs/mod.rs index bab0a98..fd8809c 100644 --- a/csflow/src/structs/mod.rs +++ b/csflow/src/structs/mod.rs @@ -1,5 +1,9 @@ mod vec3; +mod global_vars; +mod gamerules; mod entity; pub use vec3::Vec3; +pub use global_vars::GlobalVars; +pub use gamerules::GameRules; pub use entity::{CBaseEntity, CPlayerController, CPlayerPawn}; \ No newline at end of file diff --git a/radarflow/Cargo.toml b/radarflow/Cargo.toml index 737fa25..347295e 100644 --- a/radarflow/Cargo.toml +++ b/radarflow/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "radarflow" -version = "0.2.0" +version = "0.2.1" authors = ["Janek S <development@superyu.xyz>"] edition = "2021" diff --git a/radarflow/src/cli.rs b/radarflow/src/cli.rs index 737aa2b..f3b51b7 100644 --- a/radarflow/src/cli.rs +++ b/radarflow/src/cli.rs @@ -4,7 +4,6 @@ use clap::{Parser, ValueEnum}; use csflow::{Connector, memflow::Inventory}; const PORT_RANGE: std::ops::RangeInclusive<usize> = 8000..=65535; -const POLL_RANGE: std::ops::RangeInclusive<usize> = 1..=1000; #[derive(Parser)] #[command(author, version = version(), about, long_about = None)] @@ -25,10 +24,6 @@ pub struct Cli { #[arg(short, long, default_value = "./webradar", value_parser = valid_path)] pub web_path: PathBuf, - /// Polling frequency in times per second for the DMA thread - #[arg(short = 'r', long, default_value_t = 60, value_parser = poll_in_range)] - pub poll_rate: u16, - /// Verbosity level for logging to the console #[arg(value_enum, long, short, ignore_case = true, default_value_t = Loglevel::Warn)] pub loglevel: Loglevel, @@ -75,21 +70,6 @@ fn valid_path(s: &str) -> Result<PathBuf, String> { Ok(path) } -fn poll_in_range(s: &str) -> Result<u16, String> { - let port: usize = s - .parse() - .map_err(|_| format!("`{s}` isn't a valid number"))?; - if POLL_RANGE.contains(&port) { - Ok(port as u16) - } else { - Err(format!( - "not in range {}-{}", - POLL_RANGE.start(), - POLL_RANGE.end() - )) - } -} - /// Wrapper because log::LevelFilter doesn't implement ValueEnum #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Default)] pub enum Loglevel { diff --git a/radarflow/src/dma/cache.rs b/radarflow/src/dma/cache.rs index c0b580d..acac88e 100644 --- a/radarflow/src/dma/cache.rs +++ b/radarflow/src/dma/cache.rs @@ -1,4 +1,4 @@ -use csflow::{memflow::Address, enums::PlayerType}; +use csflow::{memflow::Address, enums::PlayerType, structs::{GlobalVars, GameRules}, traits::MemoryClass}; #[derive(Clone, Copy)] pub enum CachedEntity { @@ -8,54 +8,71 @@ pub enum CachedEntity { pub struct Cache { timestamp: std::time::Instant, + valid: bool, entity_cache: Vec<CachedEntity>, - map_name: String, entity_list: Address, + globals: GlobalVars, + gamerules: GameRules, } impl Cache { pub fn is_valid(&self) -> bool { - if self.timestamp.elapsed() > std::time::Duration::from_millis(250) { - return false; - } + if self.valid { + if self.timestamp.elapsed() > std::time::Duration::from_secs(60 * 3) { + log::info!("Invalidated cache! Reason: time"); + return false + } - true + true + } else { false } } pub fn new_invalid() -> Cache { Cache { timestamp: std::time::Instant::now().checked_sub(std::time::Duration::from_millis(500)).unwrap(), + valid: false, entity_cache: Vec::new(), - map_name: String::new(), entity_list: Address::null(), + globals: GlobalVars::new(Address::null()), + gamerules: GameRules::new(Address::null()), } } + pub fn invalidate(&mut self) { + self.valid = false; + } + pub fn entity_cache(&mut self) -> Vec<CachedEntity> { self.entity_cache.clone() } - pub fn map_name(&self) -> String { - self.map_name.clone() - } - pub fn entity_list(&self) -> Address { self.entity_list } + + pub fn globals(&self) -> GlobalVars { + self.globals + } + + pub fn gamerules(&self) -> GameRules { + self.gamerules + } } pub struct CacheBuilder { entity_cache: Option<Vec<CachedEntity>>, - map_name: Option<String>, - entity_list: Option<Address> + entity_list: Option<Address>, + globals: Option<GlobalVars>, + gamerules: Option<GameRules> } impl CacheBuilder { pub fn new() -> CacheBuilder { CacheBuilder { entity_cache: None, - map_name: None, entity_list: None, + globals: None, + gamerules: None, } } @@ -64,22 +81,29 @@ impl CacheBuilder { self } - pub fn map_name(mut self, map_name: String) -> CacheBuilder { - self.map_name = Some(map_name); + pub fn entity_list(mut self, entity_list: Address) -> CacheBuilder { + self.entity_list = Some(entity_list); self } - pub fn entity_list(mut self, entity_list: Address) -> CacheBuilder { - self.entity_list = Some(entity_list); + pub fn globals(mut self, globals: GlobalVars) -> CacheBuilder { + self.globals = Some(globals); + self + } + + pub fn gamerules(mut self, gamerules: GameRules) -> CacheBuilder { + self.gamerules = Some(gamerules); self } pub fn build(self) -> anyhow::Result<Cache> { Ok(Cache { timestamp: std::time::Instant::now(), + valid: true, entity_cache: self.entity_cache.ok_or(anyhow::anyhow!("entity_cache not set on builder"))?, - map_name: self.map_name.ok_or(anyhow::anyhow!("map_name not set on builder"))?, entity_list: self.entity_list.ok_or(anyhow::anyhow!("entity_list not set on builder"))?, + globals: self.globals.ok_or(anyhow::anyhow!("globals not set on builder"))?, + gamerules: self.gamerules.ok_or(anyhow::anyhow!("gamerules not set on builder"))?, }) } } \ No newline at end of file diff --git a/radarflow/src/dma/mod.rs b/radarflow/src/dma/mod.rs index 7a31d7a..bf0cd9d 100644 --- a/radarflow/src/dma/mod.rs +++ b/radarflow/src/dma/mod.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use csflow::{CheatCtx, Connector, memflow::Process, traits::{MemoryClass, BaseEntity}, enums::PlayerType, structs::{CBaseEntity, CPlayerController}}; -use tokio::{sync::RwLock, time::{Duration, Instant}}; +use tokio::{sync::RwLock, time::Duration}; use crate::{comms::{RadarData, EntityData, BombData, PlayerData}, dma::cache::CacheBuilder}; @@ -10,30 +10,22 @@ use self::cache::Cache; mod cache; const SECOND_AS_NANO: u64 = 1000*1000*1000; -static ONCE: std::sync::Once = std::sync::Once::new(); -pub async fn run(connector: Connector, pcileech_device: String, poll_rate: u16, data_lock: Arc<RwLock<RadarData>>) -> anyhow::Result<()> { +pub async fn run(connector: Connector, pcileech_device: String, data_lock: Arc<RwLock<RadarData>>) -> anyhow::Result<()> { let mut ctx = CheatCtx::setup(connector, pcileech_device)?; - println!("---------------------------------------------------"); - println!("Found cs2.exe at {:X}", ctx.process.info().address); - println!("Found engine module at cs2.exe+{:X}", ctx.engine_module.base); - println!("Found client module at cs2.exe+{:X}", ctx.client_module.base); - println!("---------------------------------------------------"); - - // Avoid printing warnings and other stuff before the initial prints are complete - tokio::time::sleep(Duration::from_millis(500)).await; - - // For poll rate timing - let should_time = poll_rate != 0; - - let target_interval = Duration::from_nanos(SECOND_AS_NANO / poll_rate as u64); - let mut last_iteration_time = Instant::now(); - let mut missmatch_count = 0; - let mut cache = Cache::new_invalid(); + let mut last_tickcount = -1; + let mut last_round = -1; + let mut last_gamephase = -1; + + // Duration for a single tick on 128 ticks. Im assuming 128 ticks because I dont fucking know how to read the current tickrate off cs2 memory lol + let target_interval = Duration::from_nanos(SECOND_AS_NANO / 128); + loop { + let start_stamp = tokio::time::Instant::now(); + if ctx.process.state().is_dead() { break; } @@ -43,8 +35,8 @@ pub async fn run(connector: Connector, pcileech_device: String, poll_rate: u16, let globals = ctx.get_globals()?; let highest_index = ctx.highest_entity_index()?; - let map_name = ctx.map_name(globals)?; let entity_list = ctx.get_entity_list()?; + let gamerules = ctx.get_gamerules()?; let local = ctx.get_local()?; @@ -87,116 +79,147 @@ pub async fn run(connector: Connector, pcileech_device: String, poll_rate: u16, } } } - + cache = CacheBuilder::new() .entity_cache(cached_entities) .entity_list(entity_list) - .map_name(map_name) + .globals(globals) + .gamerules(gamerules) .build()?; - log::debug!("Rebuilt cache."); + log::info!("Rebuilt cache."); } if ctx.network_is_ingame()? { - let mut radar_data = Vec::with_capacity(64); + // 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)?; - if ctx.is_bomb_planted()? { - let bomb = ctx.get_plantedc4()?; - let bomb_pos = bomb.pos(&mut ctx)?; - radar_data.push( - EntityData::Bomb(BombData::new( - bomb_pos, - true - )) - ); + if map_name == "<empty>" { + last_round = -1; + last_gamephase = -1; + + 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; } - for cached_data in cache.entity_cache() { - match cached_data { - cache::CachedEntity::Bomb { ptr } => { - if ctx.is_bomb_dropped()? { - let bomb_entity = CBaseEntity::new(ptr); - let pos = bomb_entity.pos(&mut ctx)?; + let cur_round = cache.gamerules().total_rounds_played(&mut ctx)?; + + // New round started, invalidate cache and run next loop + if cur_round != last_round { + last_round = cur_round; + cache.invalidate(); + log::info!("Invalidated cache! Reason: new round"); + continue; + } + + let cur_gamephase = cache.gamerules().game_phase(&mut ctx)?; + + // New game phase, invalidate cache and run next loop + if cur_gamephase != last_gamephase { + last_gamephase = cur_gamephase; + cache.invalidate(); + log::info!("Invalidated cache! Reason: new gamephase"); + 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 dont expect more than 16 entries in our radar data + let mut radar_data = Vec::with_capacity(16); + + if cache.gamerules().bomb_planted(&mut ctx)? { + let bomb = ctx.get_plantedc4()?; + let bomb_pos = bomb.pos(&mut ctx)?; + radar_data.push( + EntityData::Bomb(BombData::new( + bomb_pos, + true + )) + ); + } - radar_data.push( - EntityData::Bomb( - BombData::new( - pos, - false - ) - ) - ); - } - }, - cache::CachedEntity::Player { ptr, player_type } => { - let controller = CPlayerController::new(ptr); - if let Some(pawn) = controller.get_pawn(&mut ctx, cache.entity_list())? { - 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())?; - + for cached_data in cache.entity_cache() { + match cached_data { + cache::CachedEntity::Bomb { ptr } => { + if cache.gamerules().bomb_dropped(&mut ctx)? { + let bomb_entity = CBaseEntity::new(ptr); + let pos = bomb_entity.pos(&mut ctx)?; + radar_data.push( - EntityData::Player( - PlayerData::new( - pos, - yaw, - player_type, - has_bomb + EntityData::Bomb( + BombData::new( + pos, + false ) ) ); } - } - }, - } - } - - let mut data = data_lock.write().await; - if cache.map_name() == "<empty>" || cache.map_name().is_empty() { - *data = RadarData::empty() - } else { - *data = RadarData::new(true, cache.map_name(), radar_data) - } - } else { - let mut data = data_lock.write().await; - *data = RadarData::empty() - } - - if should_time { - let elapsed = last_iteration_time.elapsed(); - - let remaining = match target_interval.checked_sub(elapsed) { - Some(t) => t, - None => { - if missmatch_count >= 25 { - ONCE.call_once(|| { - log::warn!("Remaining time till target interval was negative more than 25 times"); - log::warn!("You should decrease your poll rate."); - log::warn!("elapsed: {}ns", elapsed.as_nanos()); - log::warn!("target: {}ns", target_interval.as_nanos()); - }); - } else { - missmatch_count += 1; + }, + cache::CachedEntity::Player { ptr, player_type } => { + let controller = CPlayerController::new(ptr); + if let Some(pawn) = controller.get_pawn(&mut ctx, cache.entity_list())? { + 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())?; + + radar_data.push( + EntityData::Player( + PlayerData::new( + pos, + yaw, + player_type, + has_bomb + ) + ) + ); + } + } + }, } - Duration::from_nanos(0) - }, - }; - - #[cfg(target_os = "linux")] - tokio_timerfd::sleep(remaining).await?; - - #[cfg(not(target_os = "linux"))] - tokio::time::sleep(remaining).await; - - log::info!("poll rate: {:.2}Hz", SECOND_AS_NANO as f64 / last_iteration_time.elapsed().as_nanos() as f64); - log::trace!("elapsed: {}ns", elapsed.as_nanos()); - log::trace!("target: {}ns", target_interval.as_nanos()); - log::trace!("missmatch count: {}", missmatch_count); + } - last_iteration_time = Instant::now(); + let mut data = data_lock.write().await; + *data = RadarData::new( + true, + map_name, + radar_data + ); + + last_tickcount = cur_tickcount; + } } + + // Elapsed time since we started our loop + let elapsed = start_stamp.elapsed(); + + let remaining = match target_interval.checked_sub(elapsed) { + // This gives us the remaining time we can sleep in our loop + Some(t) => t, + // No time left, start next loop. + None => continue + }; + + // 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()); + log::debug!("target: {}ns", target_interval.as_nanos()); } Ok(()) -} +} \ No newline at end of file diff --git a/radarflow/src/main.rs b/radarflow/src/main.rs index d67cdf8..beb9a8a 100644 --- a/radarflow/src/main.rs +++ b/radarflow/src/main.rs @@ -28,7 +28,7 @@ async fn main() -> anyhow::Result<()> { let rwlock_clone = rwlock.clone(); let dma_handle = tokio::spawn(async move { - if let Err(err) = dma::run(cli.connector, cli.pcileech_device, cli.poll_rate, rwlock_clone).await { + if let Err(err) = dma::run(cli.connector, cli.pcileech_device, rwlock_clone).await { log::error!("Error in dma thread: [{}]", err.to_string()); } });