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());
         }
     });