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:
parent
bb9ca88895
commit
f71836a763
2
Cargo.lock
generated
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.
|
||||
|
239
src/dma/mod.rs
239
src/dma/mod.rs
@ -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();
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user