Code cleanup
- Cleaned up code around entities and caching - Rewrote CLI descriptions - New ingame check. - Now the webradar will correctly identify if you are on a server or not.
This commit is contained in:
parent
62deed2706
commit
e1ff81d15d
12
src/cli.rs
12
src/cli.rs
@ -11,27 +11,27 @@ const POLL_RANGE: std::ops::RangeInclusive<usize> = 1..=1000;
|
||||
#[derive(Parser)]
|
||||
#[command(author, version = version(), about, long_about = None)]
|
||||
pub struct Cli {
|
||||
/// Connector to use for DMA
|
||||
/// Specifies the connector type for DMA
|
||||
#[clap(value_enum, short, long, ignore_case = true, default_value_t = Connector::Qemu)]
|
||||
pub connector: Connector,
|
||||
|
||||
/// Pcileech device name
|
||||
/// Name of the Pcileech device
|
||||
#[clap(long, default_value_t = String::from("FPGA"))]
|
||||
pub pcileech_device: String,
|
||||
|
||||
/// Port to run Webserver on
|
||||
/// Port number for the Webserver to run on
|
||||
#[arg(short, long, default_value_t = 8000, value_parser = port_in_range)]
|
||||
pub port: u16,
|
||||
|
||||
/// Path to serve on webserver
|
||||
/// Path to the directory served by the Webserver
|
||||
#[arg(short, long, default_value = "./web", value_parser = valid_path)]
|
||||
pub web_path: PathBuf,
|
||||
|
||||
/// How often per second the DMA thread should poll for data
|
||||
/// 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,
|
||||
|
||||
/// Loglevel verbosity
|
||||
/// Verbosity level for logging to the console
|
||||
#[arg(value_enum, long, short, ignore_case = true, default_value_t = Loglevel::Warn)]
|
||||
pub loglevel: Loglevel,
|
||||
}
|
||||
|
87
src/dma/cache.rs
Normal file
87
src/dma/cache.rs
Normal file
@ -0,0 +1,87 @@
|
||||
use memflow::types::Address;
|
||||
|
||||
use crate::structs::communication::PlayerType;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum CachedEntity {
|
||||
Bomb {ptr: Address},
|
||||
Player {ptr: Address, player_type: PlayerType},
|
||||
}
|
||||
|
||||
pub struct Cache {
|
||||
timestamp: std::time::Instant,
|
||||
entity_cache: Vec<CachedEntity>,
|
||||
map_name: String,
|
||||
entity_list: Address,
|
||||
}
|
||||
|
||||
impl Cache {
|
||||
pub fn is_valid(&self) -> bool {
|
||||
if self.timestamp.elapsed() > std::time::Duration::from_millis(250) {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn new_invalid() -> Cache {
|
||||
Cache {
|
||||
timestamp: std::time::Instant::now().checked_sub(std::time::Duration::from_millis(500)).unwrap(),
|
||||
entity_cache: Vec::new(),
|
||||
map_name: String::new(),
|
||||
entity_list: Address::null(),
|
||||
}
|
||||
}
|
||||
|
||||
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 struct CacheBuilder {
|
||||
entity_cache: Option<Vec<CachedEntity>>,
|
||||
map_name: Option<String>,
|
||||
entity_list: Option<Address>
|
||||
}
|
||||
|
||||
impl CacheBuilder {
|
||||
pub fn new() -> CacheBuilder {
|
||||
CacheBuilder {
|
||||
entity_cache: None,
|
||||
map_name: None,
|
||||
entity_list: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn entity_cache(mut self, entity_cache: Vec<CachedEntity>) -> CacheBuilder {
|
||||
self.entity_cache = Some(entity_cache);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn map_name(mut self, map_name: String) -> CacheBuilder {
|
||||
self.map_name = Some(map_name);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn entity_list(mut self, entity_list: Address) -> CacheBuilder {
|
||||
self.entity_list = Some(entity_list);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> anyhow::Result<Cache> {
|
||||
Ok(Cache {
|
||||
timestamp: std::time::Instant::now(),
|
||||
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"))?,
|
||||
})
|
||||
}
|
||||
}
|
@ -1,9 +1,14 @@
|
||||
mod cache;
|
||||
|
||||
use ::std::sync::Arc;
|
||||
|
||||
use memflow::prelude::v1::*;
|
||||
use tokio::{sync::RwLock, time::{Duration, Instant}};
|
||||
|
||||
use crate::{structs::{Connector, communication::{RadarData, PlayerType, EntityData, PlayerData, BombData}}, sdk::{self, structs::{PlayerController, Cache, MemoryClass}}};
|
||||
use crate::{structs::{Connector, communication::{RadarData, PlayerType, EntityData, PlayerData, BombData}}, sdk::{self, structs::{MemoryClass, BaseEntity, CBaseEntity, CPlayerController}}, dma::cache::CacheBuilder};
|
||||
|
||||
use self::cache::Cache;
|
||||
|
||||
|
||||
pub struct CheatCtx {
|
||||
pub process: IntoProcessInstanceArcBox<'static>,
|
||||
@ -67,48 +72,45 @@ 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();
|
||||
let mut cache = Cache::new_invalid();
|
||||
|
||||
loop {
|
||||
if ctx.process.state().is_dead() {
|
||||
break;
|
||||
}
|
||||
|
||||
if cache.is_outdated() {
|
||||
cache.clean();
|
||||
if !cache.is_valid() {
|
||||
let mut cached_entities = Vec::new();
|
||||
|
||||
let globals = sdk::get_globals(&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)?;
|
||||
|
||||
if local.pawn(&mut ctx, entity_list)?.is_some() {
|
||||
cache.push_data(sdk::structs::CachedEntityData::Player {
|
||||
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) = PlayerController::from_entity_list_v2(&mut ctx, entity_list, idx)? {
|
||||
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" => {
|
||||
cache.push_data(sdk::structs::CachedEntityData::Bomb {
|
||||
cached_entities.push(cache::CachedEntity::Bomb {
|
||||
ptr: entity.ptr()
|
||||
})
|
||||
},
|
||||
"cs_player_controller" => {
|
||||
let controller = entity.to_player_controller();
|
||||
|
||||
let player_type = {
|
||||
match entity.get_player_type(&mut ctx, &local)? {
|
||||
match controller.get_player_type(&mut ctx, &local)? {
|
||||
Some(t) => {
|
||||
if t == PlayerType::Spectator { continue } else { t }
|
||||
},
|
||||
@ -116,7 +118,7 @@ pub async fn run(connector: Connector, pcileech_device: String, poll_rate: u16,
|
||||
}
|
||||
};
|
||||
|
||||
cache.push_data(sdk::structs::CachedEntityData::Player {
|
||||
cached_entities.push(cache::CachedEntity::Player {
|
||||
ptr: entity.ptr(),
|
||||
player_type,
|
||||
})
|
||||
@ -127,9 +129,13 @@ pub async fn run(connector: Connector, pcileech_device: String, poll_rate: u16,
|
||||
}
|
||||
}
|
||||
|
||||
log::debug!("Rebuilt cache.");
|
||||
cache = CacheBuilder::new()
|
||||
.entity_cache(cached_entities)
|
||||
.entity_list(entity_list)
|
||||
.map_name(map_name)
|
||||
.build()?;
|
||||
|
||||
cache.new_time();
|
||||
log::debug!("Rebuilt cache.");
|
||||
}
|
||||
|
||||
if sdk::is_ingame(&mut ctx)? {
|
||||
@ -146,12 +152,12 @@ pub async fn run(connector: Connector, pcileech_device: String, poll_rate: u16,
|
||||
);
|
||||
}
|
||||
|
||||
for cached_data in cache.data() {
|
||||
for cached_data in cache.entity_cache() {
|
||||
match cached_data {
|
||||
sdk::structs::CachedEntityData::Bomb { ptr } => {
|
||||
cache::CachedEntity::Bomb { ptr } => {
|
||||
if sdk::is_bomb_dropped(&mut ctx)? {
|
||||
let controller = PlayerController::new(ptr);
|
||||
let pos = controller.pos(&mut ctx)?;
|
||||
let bomb_entity = CBaseEntity::new(ptr);
|
||||
let pos = bomb_entity.pos(&mut ctx)?;
|
||||
|
||||
radar_data.push(
|
||||
EntityData::Bomb(
|
||||
@ -163,13 +169,13 @@ pub async fn run(connector: Connector, pcileech_device: String, poll_rate: u16,
|
||||
);
|
||||
}
|
||||
},
|
||||
sdk::structs::CachedEntityData::Player { ptr, player_type } => {
|
||||
let controller = PlayerController::new(ptr);
|
||||
if let Some(pawn) = controller.pawn(&mut ctx, cache.common().entity_list())? {
|
||||
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.common().entity_list())?;
|
||||
let has_bomb = pawn.has_c4(&mut ctx, cache.entity_list())?;
|
||||
|
||||
radar_data.push(
|
||||
EntityData::Player(
|
||||
@ -188,7 +194,11 @@ pub async fn run(connector: Connector, pcileech_device: String, poll_rate: u16,
|
||||
}
|
||||
|
||||
let mut data = data_lock.write().await;
|
||||
*data = RadarData::new(true, cache.common().map_name(), radar_data)
|
||||
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()
|
||||
@ -221,8 +231,8 @@ pub async fn run(connector: Connector, pcileech_device: String, poll_rate: u16,
|
||||
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: {}", elapsed.as_nanos());
|
||||
log::trace!("target: {}", target_interval.as_nanos());
|
||||
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();
|
||||
|
@ -15,10 +15,6 @@ mod webserver;
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
if std::env::var("RADARFLOW_LOG").is_err() {
|
||||
std::env::set_var("RADARFLOW_LOG", "warn")
|
||||
}
|
||||
|
||||
simple_logger::SimpleLogger::new()
|
||||
.with_level(cli.loglevel.into())
|
||||
.init()
|
||||
@ -35,7 +31,6 @@ async fn main() -> anyhow::Result<()> {
|
||||
if let Err(err) = dma::run(cli.connector, cli.pcileech_device, cli.poll_rate, rwlock_clone).await {
|
||||
log::error!("Error in dma thread: {}", err.to_string());
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
tokio::spawn(async move {
|
||||
|
@ -5,17 +5,17 @@ use crate::dma::CheatCtx;
|
||||
use memflow::prelude::v1::*;
|
||||
use anyhow::Result;
|
||||
|
||||
use self::structs::{PlayerController, PlayerPawn, MemoryClass, Bomb};
|
||||
use self::structs::{CPlayerController, CBaseEntity, MemoryClass, CPlayerPawn};
|
||||
|
||||
pub fn get_local(ctx: &mut CheatCtx) -> Result<PlayerController> {
|
||||
pub fn get_local(ctx: &mut CheatCtx) -> Result<CPlayerController> {
|
||||
let ptr = ctx.process.read_addr64(ctx.client_module.base + cs2dumper::offsets::client_dll::dwLocalPlayerController)?;
|
||||
Ok(PlayerController::new(ptr))
|
||||
Ok(CPlayerController::new(ptr))
|
||||
}
|
||||
|
||||
pub fn get_plantedc4(ctx: &mut CheatCtx) -> Result<Bomb> {
|
||||
pub fn get_plantedc4(ctx: &mut CheatCtx) -> Result<CBaseEntity> {
|
||||
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))
|
||||
Ok(CBaseEntity::new(ptr2))
|
||||
}
|
||||
|
||||
pub fn is_bomb_planted(ctx: &mut CheatCtx) -> Result<bool> {
|
||||
@ -30,10 +30,16 @@ pub fn is_bomb_dropped(ctx: &mut CheatCtx) -> Result<bool> {
|
||||
Ok(data != 0)
|
||||
}
|
||||
|
||||
pub fn is_ingame(ctx: &mut CheatCtx) -> Result<bool> {
|
||||
let game_rules = ctx.process.read_addr64(ctx.client_module.base + cs2dumper::offsets::client_dll::dwGameRules)?;
|
||||
let data: i32 = ctx.process.read(game_rules + cs2dumper::client::C_CSGameRules::m_gamePhase)?;
|
||||
Ok(data != 1)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_local_pawn(ctx: &mut CheatCtx) -> Result<PlayerPawn> {
|
||||
pub fn get_local_pawn(ctx: &mut CheatCtx) -> Result<CPlayerPawn> {
|
||||
let ptr = ctx.process.read_addr64(ctx.client_module.base + cs2dumper::offsets::client_dll::dwLocalPlayerPawn)?;
|
||||
Ok(PlayerPawn::new(ptr))
|
||||
Ok(CPlayerPawn::new(ptr))
|
||||
}
|
||||
|
||||
pub fn get_entity_list(ctx: &mut CheatCtx) -> Result<Address> {
|
||||
@ -51,20 +57,16 @@ 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> {
|
||||
/*
|
||||
pub fn network_is_ingame(ctx: &mut CheatCtx) -> Result<bool> {
|
||||
let ptr = ctx.process.read_addr64(ctx.engine_module.base + cs2dumper::offsets::engine2_dll::dwNetworkGameClient)?;
|
||||
let signonstate: u64 = ctx.process.read(ptr + cs2dumper::offsets::engine2_dll::dwNetworkGameClient_signOnState)?;
|
||||
Ok(signonstate == 6)
|
||||
}
|
||||
}
|
||||
*/
|
@ -1,212 +0,0 @@
|
||||
use crate::{dma::CheatCtx, sdk::cs2dumper, structs::{Vec3, communication::PlayerType}};
|
||||
use enum_primitive_derive::Primitive;
|
||||
use memflow::{prelude::MemoryView, types::Address};
|
||||
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 {
|
||||
Spectator = 1,
|
||||
T = 2,
|
||||
CT = 3
|
||||
}
|
||||
|
||||
pub struct PlayerController(Address);
|
||||
|
||||
impl PlayerController {
|
||||
|
||||
/*
|
||||
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_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);
|
||||
}
|
||||
|
||||
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> {
|
||||
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 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_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: &PlayerController) -> Result<Option<PlayerType>> {
|
||||
if self.0 == local.0 {
|
||||
return Ok(Some(PlayerType::Local))
|
||||
}
|
||||
|
||||
let team = {
|
||||
match self.get_team(ctx)? {
|
||||
Some(t) => t,
|
||||
None => { return Ok(None) },
|
||||
}
|
||||
};
|
||||
|
||||
let local_team = {
|
||||
match local.get_team(ctx)? {
|
||||
Some(t) => t,
|
||||
None => { return Ok(None) },
|
||||
}
|
||||
};
|
||||
|
||||
let player_type = {
|
||||
if team == TeamID::Spectator {
|
||||
PlayerType::Spectator
|
||||
} else if team != local_team {
|
||||
PlayerType::Enemy
|
||||
} else {
|
||||
PlayerType::Team
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Some(player_type))
|
||||
}
|
||||
|
||||
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)?;
|
||||
PlayerPawn::from_uhandle(ctx, entity_list, uhandle)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl MemoryClass for PlayerController {
|
||||
fn ptr(&self) -> Address {
|
||||
self.0
|
||||
}
|
||||
|
||||
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 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 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)?)
|
||||
}
|
||||
}
|
50
src/sdk/structs/entity_classes/base_entity.rs
Normal file
50
src/sdk/structs/entity_classes/base_entity.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use memflow::{types::Address, mem::MemoryView};
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::{dma::CheatCtx, structs::Vec3, sdk::{cs2dumper, structs::{BaseEntity, MemoryClass}}};
|
||||
use super::CPlayerController;
|
||||
|
||||
pub struct CBaseEntity(Address);
|
||||
|
||||
impl MemoryClass for CBaseEntity {
|
||||
fn ptr(&self) -> Address {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn new(ptr: Address) -> Self {
|
||||
Self(ptr)
|
||||
}
|
||||
}
|
||||
|
||||
impl BaseEntity for CBaseEntity {
|
||||
fn from_index(ctx: &mut CheatCtx, entity_list: Address, index: i32) -> Result<Option<CBaseEntity>> {
|
||||
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);
|
||||
}
|
||||
|
||||
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)))
|
||||
}
|
||||
|
||||
fn pos(&self, ctx: &mut CheatCtx) -> anyhow::Result<Vec3> {
|
||||
let node = ctx.process.read_addr64(self.0 + cs2dumper::client::C_BaseEntity::m_pGameSceneNode)?;
|
||||
Ok(ctx.process.read(node + cs2dumper::client::CGameSceneNode::m_vecAbsOrigin)?)
|
||||
}
|
||||
|
||||
fn class_name(&self, ctx: &mut CheatCtx) -> anyhow::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)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl CBaseEntity {
|
||||
pub fn to_player_controller(&self) -> CPlayerController {
|
||||
CPlayerController::new(self.0)
|
||||
}
|
||||
}
|
26
src/sdk/structs/entity_classes/mod.rs
Normal file
26
src/sdk/structs/entity_classes/mod.rs
Normal file
@ -0,0 +1,26 @@
|
||||
mod base_entity;
|
||||
mod player_controller;
|
||||
mod player_pawn;
|
||||
|
||||
pub use base_entity::CBaseEntity;
|
||||
pub use player_controller::CPlayerController;
|
||||
pub use player_pawn::CPlayerPawn;
|
||||
|
||||
use crate::{dma::CheatCtx, structs::Vec3};
|
||||
|
||||
use memflow::types::Address;
|
||||
use anyhow::Result;
|
||||
|
||||
/// A trait that implements basic functions from C_BaseEntity
|
||||
/// CCSPlayerController inherits C_BaseEntity, which is why this trait exists.
|
||||
pub trait BaseEntity {
|
||||
fn from_index(ctx: &mut CheatCtx, entity_list: Address, index: i32) -> Result<Option<Self>> where Self: std::marker::Sized;
|
||||
fn pos(&self, ctx: &mut CheatCtx) -> Result<Vec3>;
|
||||
fn class_name(&self, ctx: &mut CheatCtx) -> Result<String>;
|
||||
}
|
||||
|
||||
/// A trait that implements basic functions for an class represented by a single pointer
|
||||
pub trait MemoryClass {
|
||||
fn ptr(&self) -> Address;
|
||||
fn new(ptr: Address) -> Self;
|
||||
}
|
91
src/sdk/structs/entity_classes/player_controller.rs
Normal file
91
src/sdk/structs/entity_classes/player_controller.rs
Normal file
@ -0,0 +1,91 @@
|
||||
use memflow::{types::Address, mem::MemoryView};
|
||||
use anyhow::Result;
|
||||
use num_traits::FromPrimitive;
|
||||
|
||||
use crate::{dma::CheatCtx, structs::{Vec3, communication::PlayerType}, sdk::{cs2dumper, structs::{BaseEntity, MemoryClass, TeamID}}};
|
||||
|
||||
use super::CPlayerPawn;
|
||||
|
||||
pub struct CPlayerController(Address);
|
||||
|
||||
impl MemoryClass for CPlayerController {
|
||||
fn ptr(&self) -> Address {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn new(ptr: Address) -> Self {
|
||||
Self(ptr)
|
||||
}
|
||||
}
|
||||
|
||||
impl BaseEntity for CPlayerController {
|
||||
fn from_index(ctx: &mut CheatCtx, entity_list: Address, index: i32) -> Result<Option<CPlayerController>> {
|
||||
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);
|
||||
}
|
||||
|
||||
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)))
|
||||
}
|
||||
|
||||
fn pos(&self, ctx: &mut CheatCtx) -> anyhow::Result<Vec3> {
|
||||
let node = ctx.process.read_addr64(self.0 + cs2dumper::client::C_BaseEntity::m_pGameSceneNode)?;
|
||||
Ok(ctx.process.read(node + cs2dumper::client::CGameSceneNode::m_vecAbsOrigin)?)
|
||||
}
|
||||
|
||||
fn class_name(&self, ctx: &mut CheatCtx) -> anyhow::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)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl CPlayerController {
|
||||
pub fn get_pawn(&self, ctx: &mut CheatCtx, entity_list: Address) -> Result<Option<CPlayerPawn>> {
|
||||
let uhandle = ctx.process.read(self.0 + cs2dumper::client::CCSPlayerController::m_hPlayerPawn)?;
|
||||
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>> {
|
||||
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: &CPlayerController) -> Result<Option<PlayerType>> {
|
||||
if self.0 == local.0 {
|
||||
return Ok(Some(PlayerType::Local))
|
||||
}
|
||||
|
||||
let team = {
|
||||
match self.get_team(ctx)? {
|
||||
Some(t) => t,
|
||||
None => { return Ok(None) },
|
||||
}
|
||||
};
|
||||
|
||||
let local_team = {
|
||||
match local.get_team(ctx)? {
|
||||
Some(t) => t,
|
||||
None => { return Ok(None) },
|
||||
}
|
||||
};
|
||||
|
||||
let player_type = {
|
||||
if team == TeamID::Spectator {
|
||||
PlayerType::Spectator
|
||||
} else if team != local_team {
|
||||
PlayerType::Enemy
|
||||
} else {
|
||||
PlayerType::Team
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Some(player_type))
|
||||
}
|
||||
}
|
81
src/sdk/structs/entity_classes/player_pawn.rs
Normal file
81
src/sdk/structs/entity_classes/player_pawn.rs
Normal file
@ -0,0 +1,81 @@
|
||||
use memflow::{types::Address, mem::MemoryView};
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::{dma::CheatCtx, structs::Vec3, sdk::{cs2dumper, structs::MemoryClass}};
|
||||
|
||||
pub struct CPlayerPawn(Address);
|
||||
|
||||
impl CPlayerPawn {
|
||||
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)))
|
||||
}
|
||||
}
|
||||
|
||||
// Todo: Optimize this function: find another way to do this
|
||||
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 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 CPlayerPawn {
|
||||
fn ptr(&self) -> Address {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn new(ptr: Address) -> Self {
|
||||
Self(ptr)
|
||||
}
|
||||
}
|
@ -1,82 +1,13 @@
|
||||
mod entity;
|
||||
pub use entity::*;
|
||||
use memflow::types::Address;
|
||||
mod entity_classes;
|
||||
|
||||
use crate::structs::communication::PlayerType;
|
||||
pub use entity_classes::*;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum CachedEntityData {
|
||||
Bomb {ptr: Address},
|
||||
Player {ptr: Address, player_type: PlayerType},
|
||||
}
|
||||
use enum_primitive_derive::Primitive;
|
||||
|
||||
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
|
||||
}
|
||||
#[repr(i32)]
|
||||
#[derive(Debug, Eq, PartialEq, Primitive)]
|
||||
pub enum TeamID {
|
||||
Spectator = 1,
|
||||
T = 2,
|
||||
CT = 3
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user