From f186b19255d521de9d3346fcfce25b900be8b3f6 Mon Sep 17 00:00:00 2001
From: Janek <development@superyu.xyz>
Date: Fri, 5 Jan 2024 00:31:11 +0100
Subject: [PATCH] optimize (not done yet)

- alpha-alpha implementation of CachedView in csflow.

- MUCH faster entity cache build time thanks to two professionally engineered functions :sunglasses:
---
 .vscode/launch.json                           |  26 ++++
 Cargo.lock                                    |   6 +-
 csflow/Cargo.toml                             |   2 +-
 csflow/src/context/cached_view.rs             | 122 ++++++++++++++++++
 csflow/src/{context.rs => context/mod.rs}     |  43 ++++--
 csflow/src/error.rs                           |   5 +-
 csflow/src/structs/entity/base_entity.rs      |  44 ++++++-
 .../src/structs/entity/player_controller.rs   |  39 +++++-
 csflow/src/structs/entity/player_pawn.rs      |  26 ++--
 csflow/src/structs/gamerules.rs               |   8 +-
 csflow/src/structs/global_vars.rs             |   5 +-
 csflow/src/traits/base_entity.rs              |   2 +
 radarflow/src/dma/cache.rs                    | 105 ++++++++++++++-
 radarflow/src/dma/mod.rs                      | 113 +++++-----------
 14 files changed, 417 insertions(+), 129 deletions(-)
 create mode 100644 .vscode/launch.json
 create mode 100644 csflow/src/context/cached_view.rs
 rename csflow/src/{context.rs => context/mod.rs} (66%)

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