Update 0.1.1

Both:
- Added full bomb ESP

Core:
- Added Address caching
 - Improves performance significantly by gathering entity addresses only every 250ms
 - Actual data like positions and angles are still gathered at the specified polling rate.

Web:
- Player bomb indicator is now the same color as planted/dropped bombs
This commit is contained in:
Janek 2023-11-28 00:59:45 +01:00
parent bb9ca88895
commit f71836a763
12 changed files with 926 additions and 819 deletions

2
Cargo.lock generated

@ -1588,7 +1588,7 @@ dependencies = [
[[package]]
name = "radarflow-cs2"
version = "0.1.0"
version = "0.1.1"
dependencies = [
"anyhow",
"axum",

@ -1,6 +1,6 @@
[package]
name = "radarflow-cs2"
version = "0.1.0"
version = "0.1.1"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

@ -1,9 +1,6 @@
# radarflow2
A Web radar for CS2 using [memflow](https://github.com/memflow/memflow)
### Features currently missing
- Bomb displayed on the radar
## How can I run this?
First, you need to set up a virtual machine on linux using qemu.
As of now, memflow's pcileech connector is not supported/tested.

@ -3,7 +3,7 @@ use ::std::sync::Arc;
use memflow::prelude::v1::*;
use tokio::{sync::RwLock, time::{Duration, Instant}};
use crate::{structs::{Connector, communication::{RadarData, PlayerType, EntityData, PlayerData}}, sdk::{self, cs2dumper, structs::{CPlayerPawn, CCSPlayerController}}};
use crate::{structs::{Connector, communication::{RadarData, PlayerType, EntityData, PlayerData, BombData}}, sdk::{self, structs::{PlayerController, Cache, MemoryClass}}};
pub struct CheatCtx {
pub process: IntoProcessInstanceArcBox<'static>,
@ -67,164 +67,131 @@ pub async fn run(connector: Connector, pcileech_device: String, poll_rate: u16,
let mut last_iteration_time = Instant::now();
let mut missmatch_count = 0;
let mut cache = Cache::new();
loop {
if ctx.process.state().is_dead() {
println!("is dead");
break;
}
if sdk::is_ingame(&mut ctx)? {
if cache.is_outdated() {
cache.clean();
let globals = sdk::get_globals(&mut ctx)?;
let entity_list = sdk::get_entity_list(&mut ctx)?;
let highest_index = sdk::highest_entity_index(&mut ctx)?;
let map_name = sdk::map_name(globals, &mut ctx)?;
let entity_list = sdk::get_entity_list(&mut ctx)?;
cache.common().update(
map_name,
entity_list
);
let local = sdk::get_local(&mut ctx)?;
let local_pawn_ptr: u32 = ctx.process.read(local.ptr() + cs2dumper::client::CCSPlayerController::m_hPlayerPawn)?;
if let Some(local_pawn) = CPlayerPawn::from_uhandle(local_pawn_ptr, entity_list, &mut ctx) {
let local_yaw = local_pawn.angles(&mut ctx)?.y;
let local_pos = local_pawn.pos(&mut ctx)?;
let mut player_data = Vec::with_capacity(64);
if local_pawn.is_alive(&mut ctx)? {
player_data.push(
EntityData::Player(
PlayerData::new(
local_pos,
local_yaw,
PlayerType::Local,
false
)
)
);
if local.pawn(&mut ctx, entity_list)?.is_some() {
cache.push_data(sdk::structs::CachedEntityData::Player {
ptr: local.ptr(),
player_type: PlayerType::Local
});
for idx in 1..highest_index {
if let Some(entity) = PlayerController::from_entity_list_v2(&mut ctx, entity_list, idx)? {
let class_name = entity.class_name(&mut ctx)?;
match class_name.as_str() {
"weapon_c4" => {
cache.push_data(sdk::structs::CachedEntityData::Bomb {
ptr: entity.ptr()
})
},
"cs_player_controller" => {
let player_type = {
match entity.get_player_type(&mut ctx, &local)? {
Some(t) => {
if t == PlayerType::Spectator { continue } else { t }
},
None => { continue },
}
};
cache.push_data(sdk::structs::CachedEntityData::Player {
ptr: entity.ptr(),
player_type,
})
}
_ => {}
}
}
}
}
let max_clients = sdk::max_clients(globals, &mut ctx)?;
log::debug!("Rebuilt cache.");
for idx in 1..max_clients {
let list_entry = ctx.process.read_addr64(entity_list + ((8 * (idx & 0x7FFF)) >> 9) + 16)?;
if list_entry.is_null() && !list_entry.is_valid() {
continue;
}
cache.new_time();
}
if sdk::is_ingame(&mut ctx)? {
let mut radar_data = Vec::with_capacity(64);
if sdk::is_bomb_planted(&mut ctx)? {
let bomb = sdk::get_plantedc4(&mut ctx)?;
let bomb_pos = bomb.pos(&mut ctx)?;
radar_data.push(
EntityData::Bomb(BombData::new(
bomb_pos,
true
))
);
}
for cached_data in cache.data() {
match cached_data {
sdk::structs::CachedEntityData::Bomb { ptr } => {
if sdk::is_bomb_dropped(&mut ctx)? {
let controller = PlayerController::new(ptr);
let pos = controller.pos(&mut ctx)?;
let player_ptr = ctx.process.read_addr64(list_entry + 120 * (idx & 0x1FF))?;
if player_ptr.is_null() && !player_ptr.is_valid() {
continue;
}
let pawn_uhandle = ctx.process.read(player_ptr + cs2dumper::client::CCSPlayerController::m_hPlayerPawn)?;
if let (Some(pawn), player) = (CPlayerPawn::from_uhandle(pawn_uhandle, entity_list, &mut ctx), CCSPlayerController::new(player_ptr)) {
if player.entity_identity(&mut ctx)?.designer_name(&mut ctx)? == "cs_player_controller" && pawn.is_alive(&mut ctx)? {
let pos = pawn.pos(&mut ctx)?;
let angles = pawn.angles(&mut ctx)?;
let player_type = {
match player.get_player_type(&mut ctx, &local)? {
Some(t) => {
if t == PlayerType::Spectator { continue } else { t }
},
None => { continue },
}
};
player_data.push(
EntityData::Player(
PlayerData::new(
pos,
angles.y,
player_type,
radar_data.push(
EntityData::Bomb(
BombData::new(
pos,
false
)
)
);
}
}
}
let mut data = data_lock.write().await;
*data = RadarData::new(true, map_name, player_data, local_yaw)
}
//let local_pawn = sdk::get_local_pawn(&mut ctx)?;
//let local_pawn = CPlayerPawn::new(local_cs_player_pawn);
/*
let mut next_ent = {
let mut iter_ent = local.to_base();
while iter_ent.entity_identity(&mut ctx)?.prev_by_class(&mut ctx).is_ok() {
iter_ent = iter_ent.entity_identity(&mut ctx)?.prev_by_class(&mut ctx)?;
}
iter_ent
};
let mut count = 0;
let mut pawn_count = 0;
println!("prev by class ok? {}", next_ent.entity_identity(&mut ctx)?.prev_by_class(&mut ctx).is_ok());
while next_ent.entity_identity(&mut ctx)?.next_by_class(&mut ctx).is_ok() {
count += 1;
let pawn = next_ent.to_controller().pawn(entity_list, &mut ctx)?;
if let Some(p) = pawn {
pawn_count += 1;
if !p.is_alive(&mut ctx)? {
next_ent = next_ent.entity_identity(&mut ctx).unwrap().next_by_class(&mut ctx).unwrap();
continue
}
let pos = p.pos(&mut ctx)?;
let angles = p.angles(&mut ctx)?;
let player_type = {
match next_ent.to_controller().get_player_type(&mut ctx, &local)? {
Some(t) => {
if t == PlayerType::Spectator {
next_ent = next_ent.entity_identity(&mut ctx).unwrap().next_by_class(&mut ctx).unwrap();
continue
} else { t }
},
None => {
next_ent = next_ent.entity_identity(&mut ctx).unwrap().next_by_class(&mut ctx).unwrap();
continue
},
},
sdk::structs::CachedEntityData::Player { ptr, player_type } => {
let controller = PlayerController::new(ptr);
if let Some(pawn) = controller.pawn(&mut ctx, cache.common().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.common().entity_list())?;
radar_data.push(
EntityData::Player(
PlayerData::new(
pos,
yaw,
player_type,
has_bomb
)
)
);
}
}
};
player_data.push(
EntityData::Player(
PlayerData::new(
pos,
angles.y,
player_type,
false
)
)
);
},
}
//let pawn = next_ent.to_controller().pawn2(entity_list, &mut ctx)?;
next_ent = next_ent.entity_identity(&mut ctx)?.next_by_class(&mut ctx)?;
}
println!("next by class ok? {}", next_ent.entity_identity(&mut ctx)?.next_by_class(&mut ctx).is_ok());
*/
let mut data = data_lock.write().await;
*data = RadarData::new(true, cache.common().map_name(), radar_data)
} else {
let mut data = data_lock.write().await;
*data = RadarData::empty();
*data = RadarData::empty()
}
if should_time {
@ -253,7 +220,7 @@ pub async fn run(connector: Connector, pcileech_device: String, poll_rate: u16,
#[cfg(not(target_os = "linux"))]
tokio::time::sleep(remaining).await;
log::trace!("polling at {:.2}Hz", SECOND_AS_NANO as f64 / last_iteration_time.elapsed().as_nanos() as f64);
log::info!("poll rate: {:.2}Hz", SECOND_AS_NANO as f64 / last_iteration_time.elapsed().as_nanos() as f64);
log::trace!("elapsed: {}", elapsed.as_nanos());
log::trace!("target: {}", target_interval.as_nanos());
log::trace!("missmatch count: {}", missmatch_count);
@ -262,7 +229,5 @@ pub async fn run(connector: Connector, pcileech_device: String, poll_rate: u16,
}
}
println!("DMA loop exited for some reason");
Ok(())
}

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
/*
* Created using https://github.com/a2x/cs2-dumper
* Fri, 27 Oct 2023 01:03:22 +0000
* Tue, 21 Nov 2023 00:47:41 +0000
*/
#![allow(non_snake_case, non_upper_case_globals)]
@ -175,6 +175,11 @@ pub mod EventFrameBoundary_t {
pub const m_flFrameTime: usize = 0x0; // float
}
pub mod EventHostTimescaleChanged_t {
pub const m_flOldValue: usize = 0x0; // float
pub const m_flNewValue: usize = 0x4; // float
}
pub mod EventModInitialized_t {
}

@ -1,46 +1,47 @@
/*
* Created using https://github.com/a2x/cs2-dumper
* Mon, 30 Oct 2023 00:17:09 +0000
* Tue, 21 Nov 2023 00:47:43 +0000
*/
#![allow(non_snake_case, non_upper_case_globals)]
pub mod client_dll { // client.dll
pub const dwBaseEntityModel_setModel: usize = 0x57EA50;
pub const dwEntityList: usize = 0x17995C0;
pub const dwForceAttack: usize = 0x169EE60;
pub const dwForceAttack2: usize = 0x169EEF0;
pub const dwForceBackward: usize = 0x169F130;
pub const dwForceCrouch: usize = 0x169F400;
pub const dwForceForward: usize = 0x169F0A0;
pub const dwForceJump: usize = 0x169F370;
pub const dwForceLeft: usize = 0x169F1C0;
pub const dwForceRight: usize = 0x169F250;
pub const dwGameEntitySystem: usize = 0x1952588;
pub const dwGameEntitySystem_getBaseEntity: usize = 0x602050;
pub const dwGameEntitySystem_getHighestEntityIndex: usize = 0x5F3D40;
pub const dwGameRules: usize = 0x17F5488;
pub const dwGlobalVars: usize = 0x169AFE0;
pub const dwGlowManager: usize = 0x17F4C10;
pub const dwInterfaceLinkList: usize = 0x1980298;
pub const dwLocalPlayerController: usize = 0x17E8158;
pub const dwLocalPlayerPawn: usize = 0x1886C48;
pub const dwPlantedC4: usize = 0x188BFE0;
pub const dwViewAngles: usize = 0x18E6770;
pub const dwViewMatrix: usize = 0x1887730;
pub const dwViewRender: usize = 0x1888128;
pub const dwEntityList: usize = 0x17B5200;
pub const dwForceAttack: usize = 0x16B5510;
pub const dwForceAttack2: usize = 0x16B55A0;
pub const dwForceBackward: usize = 0x16B57E0;
pub const dwForceCrouch: usize = 0x16B5AB0;
pub const dwForceForward: usize = 0x16B5750;
pub const dwForceJump: usize = 0x16B5A20;
pub const dwForceLeft: usize = 0x16B5870;
pub const dwForceRight: usize = 0x16B5900;
pub const dwGameEntitySystem: usize = 0x18E08E0;
pub const dwGameEntitySystem_getHighestEntityIndex: usize = 0x1510;
pub const dwGameRules: usize = 0x1810EB0;
pub const dwGlobalVars: usize = 0x16B1670;
pub const dwGlowManager: usize = 0x1810ED8;
pub const dwInterfaceLinkList: usize = 0x190E578;
pub const dwLocalPlayerController: usize = 0x1804518;
pub const dwLocalPlayerPawn: usize = 0x16BC5B8;
pub const dwPlantedC4: usize = 0x1818478;
pub const dwPrediction: usize = 0x16BC480;
pub const dwSensitivity: usize = 0x1812468;
pub const dwSensitivity_sensitivity: usize = 0x40;
pub const dwViewAngles: usize = 0x18744C0;
pub const dwViewMatrix: usize = 0x1813840;
pub const dwViewRender: usize = 0x18140C0;
}
pub mod engine2_dll { // engine2.dll
pub const dwBuildNumber: usize = 0x488514;
pub const dwNetworkGameClient: usize = 0x487AB0;
pub const dwBuildNumber: usize = 0x48B514;
pub const dwNetworkGameClient: usize = 0x48AAC0;
pub const dwNetworkGameClient_getLocalPlayer: usize = 0xF0;
pub const dwNetworkGameClient_maxClients: usize = 0x250;
pub const dwNetworkGameClient_signOnState: usize = 0x240;
pub const dwWindowHeight: usize = 0x5396DC;
pub const dwWindowWidth: usize = 0x5396D8;
pub const dwWindowHeight: usize = 0x541D8C;
pub const dwWindowWidth: usize = 0x541D88;
}
pub mod inputsystem_dll { // inputsystem.dll
pub const dwInputSystem: usize = 0x35770;
pub const dwInputSystem: usize = 0x35760;
}

@ -5,16 +5,35 @@ use crate::dma::CheatCtx;
use memflow::prelude::v1::*;
use anyhow::Result;
use self::structs::{CCSPlayerController, CPlayerPawn};
use self::structs::{PlayerController, PlayerPawn, MemoryClass, Bomb};
pub fn get_local(ctx: &mut CheatCtx) -> Result<CCSPlayerController> {
pub fn get_local(ctx: &mut CheatCtx) -> Result<PlayerController> {
let ptr = ctx.process.read_addr64(ctx.client_module.base + cs2dumper::offsets::client_dll::dwLocalPlayerController)?;
Ok(CCSPlayerController::new(ptr))
Ok(PlayerController::new(ptr))
}
pub fn get_local_pawn(ctx: &mut CheatCtx) -> Result<CPlayerPawn> {
pub fn get_plantedc4(ctx: &mut CheatCtx) -> Result<Bomb> {
let ptr = ctx.process.read_addr64(ctx.client_module.base + cs2dumper::offsets::client_dll::dwPlantedC4)?;
let ptr2 = ctx.process.read_addr64(ptr)?;
Ok(Bomb::new(ptr2))
}
pub fn is_bomb_planted(ctx: &mut CheatCtx) -> Result<bool> {
let game_rules = ctx.process.read_addr64(ctx.client_module.base + cs2dumper::offsets::client_dll::dwGameRules)?;
let data: u8 = ctx.process.read(game_rules + cs2dumper::client::C_CSGameRules::m_bBombPlanted)?;
Ok(data != 0)
}
pub fn is_bomb_dropped(ctx: &mut CheatCtx) -> Result<bool> {
let game_rules = ctx.process.read_addr64(ctx.client_module.base + cs2dumper::offsets::client_dll::dwGameRules)?;
let data: u8 = ctx.process.read(game_rules + cs2dumper::client::C_CSGameRules::m_bBombDropped)?;
Ok(data != 0)
}
#[allow(dead_code)]
pub fn get_local_pawn(ctx: &mut CheatCtx) -> Result<PlayerPawn> {
let ptr = ctx.process.read_addr64(ctx.client_module.base + cs2dumper::offsets::client_dll::dwLocalPlayerPawn)?;
Ok(CPlayerPawn::new(ptr))
Ok(PlayerPawn::new(ptr))
}
pub fn get_entity_list(ctx: &mut CheatCtx) -> Result<Address> {
@ -32,9 +51,17 @@ pub fn map_name(global_vars: Address, ctx: &mut CheatCtx) -> Result<String> {
Ok(ctx.process.read_char_string_n(ptr, 32)?)
}
/*
pub fn max_clients(global_vars: Address, ctx: &mut CheatCtx) -> Result<i32> {
Ok(ctx.process.read(global_vars + 0x10)?)
}
*/
pub fn highest_entity_index(ctx: &mut CheatCtx) -> Result<i32> {
let game_entity_system = ctx.process.read_addr64(ctx.client_module.base + cs2dumper::offsets::client_dll::dwGameEntitySystem)?;
let highest_index = ctx.process.read(game_entity_system + cs2dumper::offsets::client_dll::dwGameEntitySystem_getHighestEntityIndex)?;
Ok(highest_index)
}
pub fn is_ingame(ctx: &mut CheatCtx) -> Result<bool> {
let ptr = ctx.process.read_addr64(ctx.engine_module.base + cs2dumper::offsets::engine2_dll::dwNetworkGameClient)?;

@ -1,9 +1,14 @@
use crate::{dma::CheatCtx, sdk::cs2dumper, structs::{Vec3, communication::PlayerType}};
use enum_primitive_derive::Primitive;
use memflow::{prelude::MemoryView, types::Address};
use anyhow::{Result, anyhow};
use anyhow::Result;
use num_traits::FromPrimitive;
pub trait MemoryClass {
fn ptr(&self) -> Address;
fn new(ptr: Address) -> Self;
}
#[repr(i32)]
#[derive(Debug, Eq, PartialEq, Primitive)]
pub enum TeamID {
@ -12,143 +17,57 @@ pub enum TeamID {
CT = 3
}
pub struct CEntityIdentity(Address);
pub struct PlayerController(Address);
impl CEntityIdentity {
pub fn prev_by_class(&self, ctx: &mut CheatCtx) -> Result<CBaseEntity> {
let prev1 = ctx.process.read_addr64(self.0 + cs2dumper::client::CEntityIdentity::m_pPrevByClass)?;
let prev = ctx.process.read_addr64(prev1)?;
if prev.is_null() || !prev.is_valid() {
Err(anyhow!("Invalid or Null"))
} else {
Ok(CBaseEntity(prev))
}
}
pub fn next_by_class(&self, ctx: &mut CheatCtx) -> Result<CBaseEntity> {
let next1 = ctx.process.read_addr64(self.0 + cs2dumper::client::CEntityIdentity::m_pNextByClass)?;
let next = ctx.process.read_addr64(next1)?;
if next.is_null() || !next.is_valid() {
Err(anyhow!("Invalid or Null"))
} else {
Ok(CBaseEntity(next))
}
}
pub fn designer_name(&self, ctx: &mut CheatCtx) -> Result<String> {
let ptr = ctx.process.read_addr64(self.0 + cs2dumper::client::CEntityIdentity::m_designerName)?;
Ok(ctx.process.read_char_string_n(ptr, 32)?)
}
}
pub struct CBaseEntity(Address);
impl CBaseEntity {
pub fn new(ptr: Address) -> CBaseEntity {
CBaseEntity(ptr)
}
pub fn ptr(&self) -> Address {
self.0
}
pub fn to_controller(&self) -> CCSPlayerController {
CCSPlayerController(self.0)
}
pub fn get_from_list(ctx: &mut CheatCtx, entity_list: Address, idx: usize) -> Result<CBaseEntity> {
let list_entry = ctx.process.read_addr64(entity_list + ((( idx & 0x7FFF ) >> 9 ) * 0x8)).unwrap();
if list_entry.is_null() || !list_entry.is_valid() {
Err(anyhow!("Invalid or Null"))
} else {
let ptr = ctx.process.read_addr64(list_entry + 120 * (idx & 0x1FF)).unwrap();
Ok(CBaseEntity(ptr))
}
}
pub fn entity_identity(&self, ctx: &mut CheatCtx) -> Result<CEntityIdentity> {
let ptr = ctx.process.read_addr64(self.0 + cs2dumper::client::CEntityInstance::m_pEntity)?;
Ok(CEntityIdentity(ptr))
}
}
pub struct CPlayerPawn(Address);
impl CPlayerPawn {
pub fn new(ptr: Address) -> CPlayerPawn {
CPlayerPawn(ptr)
}
pub fn from_uhandle(uhandle: u32, entity_list: Address, ctx: &mut CheatCtx) -> Option<CPlayerPawn> {
let list_entry = ctx.process.read_addr64(entity_list + 0x8 * ((uhandle & 0x7FFF) >> 9) + 16).unwrap();
if list_entry.is_null() || !list_entry.is_valid() {
None
} else {
let ptr = ctx.process.read_addr64(list_entry + 120 * (uhandle & 0x1FF)).unwrap();
Some(CPlayerPawn(ptr))
}
}
impl PlayerController {
/*
DWORD64 entityPawnBase = Memory::Read<unsigned __int64>(EntitiesList + ((hEntity & 0x7FFF) * ENTITY_SPACING));
auto pawn = read<C_CSPlayerPawnBase*>(entityPawnBase + 0x78 * (hEntity & 0x1FF));
pub fn from_entity_list(ctx: &mut CheatCtx, entity_list: Address, index: i32) -> Result<Option<Self>> {
let list_entry = ctx.process.read_addr64(entity_list + ((8 * (index & 0x7FFF)) >> 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))?;
if player_ptr.is_null() && !player_ptr.is_valid() {
return Ok(None);
}
Ok(Some(Self::new(player_ptr)))
}
*/
pub fn from_uhandle2(uhandle: u32, entity_list: Address, ctx: &mut CheatCtx) -> Option<CPlayerPawn> {
let ent_pawn_base = ctx.process.read_addr64(entity_list + (uhandle & 0x7FFF) * 0x10).unwrap();
if ent_pawn_base.is_null() || !ent_pawn_base.is_valid() {
None
} else {
let ptr = ctx.process.read_addr64(ent_pawn_base + 0x78 * (uhandle & 0x1FF)).unwrap();
Some(CPlayerPawn(ptr))
pub fn from_entity_list_v2(ctx: &mut CheatCtx, entity_list: Address, index: i32) -> Result<Option<Self>> {
let list_entry = ctx.process.read_addr64(entity_list + 8 * (index >> 9) + 16)?;
if list_entry.is_null() && !list_entry.is_valid() {
return Ok(None);
}
}
pub fn ptr(&self) -> Address {
self.0
let player_ptr = ctx.process.read_addr64(list_entry + 120 * (index & 0x1FF))?;
if player_ptr.is_null() && !player_ptr.is_valid() {
return Ok(None);
}
Ok(Some(Self::new(player_ptr)))
}
pub fn pos(&self, ctx: &mut CheatCtx) -> Result<Vec3> {
Ok(ctx.process.read(self.0 + cs2dumper::client::C_BasePlayerPawn::m_vOldOrigin)?)
let node = ctx.process.read_addr64(self.0 + cs2dumper::client::C_BaseEntity::m_pGameSceneNode)?;
Ok(ctx.process.read(node + cs2dumper::client::CGameSceneNode::m_vecAbsOrigin)?)
}
pub fn angles(&self, ctx: &mut CheatCtx) -> Result<Vec3> {
Ok(ctx.process.read(self.0 + cs2dumper::client::C_CSPlayerPawnBase::m_angEyeAngles)?)
}
pub fn health(&self, ctx: &mut CheatCtx) -> Result<u32> {
Ok(ctx.process.read(self.0 + cs2dumper::client::C_BaseEntity::m_iHealth)?)
}
/// Same as ::get_health > 0
pub fn is_alive(&self, ctx: &mut CheatCtx) -> Result<bool> {
Ok(self.health(ctx)? > 0)
}
}
pub struct CCSPlayerController(Address);
impl CCSPlayerController {
pub fn ptr(&self) -> Address {
self.0
}
pub fn new(ptr: Address) -> CCSPlayerController {
CCSPlayerController(ptr)
pub fn class_name(&self, ctx: &mut CheatCtx) -> Result<String> {
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)?)
}
pub fn get_team(&self, ctx: &mut CheatCtx) -> Result<Option<TeamID>> {
let team: i32 = ctx.process.read(self.0 + cs2dumper::client::C_BaseEntity::m_iTeamNum)?;
Ok(TeamID::from_i32(team))
let team_num: i32 = ctx.process.read(self.0 + cs2dumper::client::C_BaseEntity::m_iTeamNum)?;
Ok(TeamID::from_i32(team_num))
}
pub fn get_player_type(&self, ctx: &mut CheatCtx, local: &CCSPlayerController) -> Result<Option<PlayerType>> {
pub fn get_player_type(&self, ctx: &mut CheatCtx, local: &PlayerController) -> Result<Option<PlayerType>> {
if self.0 == local.0 {
return Ok(Some(PlayerType::Local))
}
@ -180,27 +99,114 @@ impl CCSPlayerController {
Ok(Some(player_type))
}
pub fn pawn(&self, entity_list: Address, ctx: &mut CheatCtx) -> Result<Option<CPlayerPawn>> {
pub fn pawn(&self, ctx: &mut CheatCtx, entity_list: Address) -> Result<Option<PlayerPawn>> {
let uhandle = ctx.process.read(self.0 + cs2dumper::client::CCSPlayerController::m_hPlayerPawn)?;
Ok(CPlayerPawn::from_uhandle(uhandle, entity_list, ctx))
PlayerPawn::from_uhandle(ctx, entity_list, uhandle)
}
pub fn pawn2(&self, entity_list: Address, ctx: &mut CheatCtx) -> Result<Option<CPlayerPawn>> {
let uhandle = ctx.process.read(self.0 + cs2dumper::client::CBasePlayerController::m_hPawn)?;
Ok(CPlayerPawn::from_uhandle2(uhandle, entity_list, ctx))
}
impl MemoryClass for PlayerController {
fn ptr(&self) -> Address {
self.0
}
pub fn player_name(&self, ctx: &mut CheatCtx) -> Result<String> {
let ptr = ctx.process.read_addr64(self.0 + cs2dumper::client::CCSPlayerController::m_sSanitizedPlayerName)?;
Ok(ctx.process.read_char_string_n(ptr, 32)?)
fn new(ptr: Address) -> Self {
Self(ptr)
}
}
pub struct PlayerPawn(Address);
impl PlayerPawn {
pub fn from_uhandle(ctx: &mut CheatCtx, entity_list: Address, uhandle: u32) -> Result<Option<Self>> {
let list_entry = ctx.process.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))?;
Ok(Some(Self(ptr)))
}
}
pub fn entity_identity(&self, ctx: &mut CheatCtx) -> Result<CEntityIdentity> {
let ptr = ctx.process.read_addr64(self.0 + cs2dumper::client::CEntityInstance::m_pEntity)?;
Ok(CEntityIdentity(ptr))
pub fn has_c4(&self, ctx: &mut CheatCtx, entity_list: Address) -> Result<bool> {
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)?;
for wep_idx in 0..wep_count {
let handle: i32 = ctx.process.read(wep_base + wep_idx * 0x4)?;
if handle == -1 {
continue;
}
let list_entry = ctx.process.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))?;
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)?;
if id == 7 {
has_c4 = true;
break;
}
}
}
Ok(has_c4)
}
pub fn to_base(&self) -> CBaseEntity {
CBaseEntity(self.0)
pub fn pos(&self, ctx: &mut CheatCtx) -> Result<Vec3> {
Ok(ctx.process.read(self.0 + cs2dumper::client::C_BasePlayerPawn::m_vOldOrigin)?)
}
pub fn angles(&self, ctx: &mut CheatCtx) -> Result<Vec3> {
Ok(ctx.process.read(self.0 + cs2dumper::client::C_CSPlayerPawnBase::m_angEyeAngles)?)
}
pub fn health(&self, ctx: &mut CheatCtx) -> Result<u32> {
Ok(ctx.process.read(self.0 + cs2dumper::client::C_BaseEntity::m_iHealth)?)
}
/// Same as ::get_health > 0
pub fn is_alive(&self, ctx: &mut CheatCtx) -> Result<bool> {
Ok(self.health(ctx)? > 0)
}
}
impl MemoryClass for PlayerPawn {
fn ptr(&self) -> Address {
self.0
}
fn new(ptr: Address) -> Self {
Self(ptr)
}
}
pub struct Bomb(Address);
impl MemoryClass for Bomb {
fn ptr(&self) -> Address {
self.0
}
fn new(ptr: Address) -> Self {
Self(ptr)
}
}
impl Bomb {
pub fn pos(&self, ctx: &mut CheatCtx) -> Result<Vec3> {
let c4_node = ctx.process.read_addr64(self.0 + cs2dumper::client::C_BaseEntity::m_pGameSceneNode)?;
Ok(ctx.process.read(c4_node + cs2dumper::client::CGameSceneNode::m_vecAbsOrigin)?)
}
}

@ -1,2 +1,82 @@
mod entity;
pub use entity::*;
pub use entity::*;
use memflow::types::Address;
use crate::structs::communication::PlayerType;
#[derive(Clone, Copy)]
pub enum CachedEntityData {
Bomb {ptr: Address},
Player {ptr: Address, player_type: PlayerType},
}
pub struct CommonCache {
map_name: String,
entity_list: Address,
}
impl CommonCache {
pub fn new() -> CommonCache {
CommonCache {
map_name: String::from("unknown"),
entity_list: Address::null(),
}
}
pub fn update(&mut self, map_name: String, entity_list: Address) {
self.map_name = map_name;
self.entity_list = entity_list;
}
pub fn map_name(&self) -> String {
self.map_name.clone()
}
pub fn entity_list(&self) -> Address {
self.entity_list
}
}
pub struct Cache {
last_cached: std::time::Instant,
data: Vec<CachedEntityData>,
common: CommonCache
}
impl Cache {
pub fn new() -> Cache {
Cache {
last_cached: std::time::Instant::now().checked_sub(std::time::Duration::from_millis(500)).unwrap(),
data: Vec::new(),
common: CommonCache::new(),
}
}
pub fn is_outdated(&self) -> bool {
if self.last_cached.elapsed() > std::time::Duration::from_millis(250) {
return true;
}
false
}
pub fn new_time(&mut self) {
self.last_cached = std::time::Instant::now();
}
pub fn clean(&mut self) {
self.data.clear();
}
pub fn data(&self) -> Vec<CachedEntityData> {
self.data.clone()
}
pub fn push_data(&mut self, data: CachedEntityData) {
self.data.push(data);
}
pub fn common(&mut self) -> &mut CommonCache {
&mut self.common
}
}

@ -26,6 +26,7 @@ pub struct BombData {
is_planted: bool
}
#[allow(dead_code)]
impl BombData {
pub fn new(pos: Vec3, is_planted: bool) -> BombData {
BombData { pos, is_planted }
@ -38,7 +39,7 @@ pub enum EntityData {
Bomb(BombData)
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default, PartialEq)]
pub enum PlayerType {
#[default]
Unknown,
@ -58,13 +59,13 @@ pub struct RadarData {
#[serde(rename(serialize = "entityData"))]
player_data: Vec<EntityData>,
#[serde(rename(serialize = "localYaw"))]
local_yaw: f32,
//#[serde(rename(serialize = "localYaw"))]
//local_yaw: f32,
}
impl RadarData {
pub fn new(ingame: bool, map_name: String, player_data: Vec<EntityData>, local_yaw: f32) -> RadarData {
RadarData { ingame, map_name, player_data, local_yaw }
pub fn new(ingame: bool, map_name: String, player_data: Vec<EntityData>) -> RadarData {
RadarData { ingame, map_name, player_data }
}
/// Returns empty RadarData, it's also the same data that gets sent to clients when not ingame
@ -73,7 +74,6 @@ impl RadarData {
ingame: false,
map_name: String::new(),
player_data: Vec::new(),
local_yaw: 0.0,
}
}
}

@ -268,7 +268,7 @@ function drawEntity(pos, fillStyle, dormant, hasBomb, yaw) {
if (hasBomb) {
ctx.beginPath();
ctx.arc(pos.x, pos.y, circleRadius / 2, 0, 2 * Math.PI);
ctx.fillStyle = "#dbb81d";
ctx.fillStyle = bombColor;
ctx.fill();
}