Update
csflow: - Create structs for gamerules and global vars radarflow: - new dma loop with less frequent cache invalidation - The new loop tries to run at a fixed 128 hz. Thats the max tickrate in cs2. The data is also only updated when a tick change is detected, so that should keep data fetching to a minimum. - todo: more testing for cache invalidation
This commit is contained in:
parent
0f0f7232fb
commit
7c652cb984
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -477,7 +477,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "csflow"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"dataview 1.0.1",
|
||||
@ -1474,7 +1474,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "radarflow"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "csflow"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
authors = ["Janek S <development@superyu.xyz>"]
|
||||
edition = "2021"
|
||||
description = "SDK for CS2 cheats utilizing memflow"
|
||||
|
@ -1,6 +1,6 @@
|
||||
use memflow::{plugins::{IntoProcessInstanceArcBox, Inventory, ConnectorArgs, args::Args}, os::{ModuleInfo, Os, Process}, mem::MemoryView, types::Address};
|
||||
|
||||
use crate::{error::Error, structs::{CPlayerController, CBaseEntity}, cs2dumper, traits::MemoryClass};
|
||||
use crate::{error::Error, structs::{CPlayerController, CBaseEntity, GlobalVars, GameRules}, cs2dumper, traits::MemoryClass};
|
||||
|
||||
pub struct CheatCtx {
|
||||
pub process: IntoProcessInstanceArcBox<'static>,
|
||||
@ -71,39 +71,30 @@ impl CheatCtx {
|
||||
Ok(CBaseEntity::new(ptr2))
|
||||
}
|
||||
|
||||
pub fn is_bomb_planted(&mut self) -> Result<bool, Error> {
|
||||
let game_rules = self.process.read_addr64(self.client_module.base + cs2dumper::offsets::client_dll::dwGameRules)?;
|
||||
let data: u8 = self.process.read(game_rules + cs2dumper::client::C_CSGameRules::m_bBombPlanted)?;
|
||||
Ok(data != 0)
|
||||
}
|
||||
|
||||
pub fn is_bomb_dropped(&mut self) -> Result<bool, Error> {
|
||||
let game_rules = self.process.read_addr64(self.client_module.base + cs2dumper::offsets::client_dll::dwGameRules)?;
|
||||
let data: u8 = self.process.read(game_rules + cs2dumper::client::C_CSGameRules::m_bBombDropped)?;
|
||||
Ok(data != 0)
|
||||
pub fn get_globals(&mut self) -> Result<GlobalVars, Error> {
|
||||
let ptr = self.process.read_addr64(self.client_module.base + cs2dumper::offsets::client_dll::dwGlobalVars)?;
|
||||
Ok(GlobalVars::new(ptr))
|
||||
}
|
||||
|
||||
pub fn get_gamerules(&mut self) -> Result<GameRules, Error> {
|
||||
let ptr = self.process.read_addr64(self.client_module.base + cs2dumper::offsets::client_dll::dwGameRules)?;
|
||||
Ok(GameRules::new(ptr))
|
||||
}
|
||||
|
||||
// todo: seperate into own class
|
||||
pub fn get_entity_list(&mut self) -> Result<Address, Error> {
|
||||
let ptr = self.process.read_addr64(self.client_module.base + cs2dumper::offsets::client_dll::dwEntityList)?;
|
||||
Ok(ptr)
|
||||
}
|
||||
|
||||
pub fn get_globals(&mut self) -> Result<Address, Error> {
|
||||
let ptr = self.process.read_addr64(self.client_module.base + cs2dumper::offsets::client_dll::dwGlobalVars)?;
|
||||
Ok(ptr)
|
||||
}
|
||||
|
||||
pub fn map_name(&mut self, global_vars: Address) -> Result<String, Error> {
|
||||
let ptr = self.process.read_addr64(global_vars + 0x188)?;
|
||||
Ok(self.process.read_char_string_n(ptr, 32)?)
|
||||
}
|
||||
|
||||
// todo: seperate into own class
|
||||
pub fn highest_entity_index(&mut self) -> Result<i32, Error> {
|
||||
let game_entity_system = self.process.read_addr64(self.client_module.base + cs2dumper::offsets::client_dll::dwGameEntitySystem)?;
|
||||
let highest_index = self.process.read(game_entity_system + cs2dumper::offsets::client_dll::dwGameEntitySystem_getHighestEntityIndex)?;
|
||||
Ok(highest_index)
|
||||
}
|
||||
|
||||
// todo: seperate into own class
|
||||
pub fn network_is_ingame(&mut self) -> Result<bool, Error> {
|
||||
let ptr = self.process.read_addr64(self.engine_module.base + cs2dumper::offsets::engine2_dll::dwNetworkGameClient)?;
|
||||
let signonstate: i32 = self.process.read(ptr + cs2dumper::offsets::engine2_dll::dwNetworkGameClient_signOnState)?;
|
||||
|
@ -28,6 +28,9 @@ pub enum Error {
|
||||
#[error("memflow partial error when reading u32: {0}")]
|
||||
MemflowPartialu32(#[from] memflow::error::PartialError<u32>),
|
||||
|
||||
#[error("memflow partial error when reading f32: {0}")]
|
||||
MemflowPartialf32(#[from] memflow::error::PartialError<f32>),
|
||||
|
||||
#[error("memflow partial error when reading u8: {0}")]
|
||||
MemflowPartialu8(#[from] memflow::error::PartialError<u8>)
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
use memflow::{types::Address, mem::MemoryView};
|
||||
use crate::{CheatCtx, Error, cs2dumper, traits::{BaseEntity, MemoryClass}, structs::Vec3};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct CBaseEntity(Address);
|
||||
|
||||
impl MemoryClass for CBaseEntity {
|
||||
|
@ -3,6 +3,7 @@ use num_traits::FromPrimitive;
|
||||
|
||||
use crate::{CheatCtx, Error, cs2dumper, structs::Vec3, traits::{MemoryClass, BaseEntity}, enums::{TeamID, PlayerType}};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct CPlayerController(Address);
|
||||
|
||||
impl MemoryClass for CPlayerController {
|
||||
|
@ -2,6 +2,7 @@ use memflow::{types::Address, mem::MemoryView};
|
||||
|
||||
use crate::{Error, CheatCtx, cs2dumper, structs::Vec3, traits::MemoryClass};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct CPlayerPawn(Address);
|
||||
|
||||
impl MemoryClass for CPlayerPawn {
|
||||
|
35
csflow/src/structs/gamerules.rs
Normal file
35
csflow/src/structs/gamerules.rs
Normal file
@ -0,0 +1,35 @@
|
||||
use memflow::{types::Address, mem::MemoryView};
|
||||
use crate::{traits::MemoryClass, CheatCtx, Error, cs2dumper};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct GameRules(Address);
|
||||
|
||||
impl MemoryClass for GameRules {
|
||||
fn ptr(&self) -> memflow::types::Address {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn new(ptr: memflow::types::Address) -> Self {
|
||||
Self(ptr)
|
||||
}
|
||||
}
|
||||
|
||||
impl GameRules {
|
||||
pub fn bomb_dropped(&self, ctx: &mut CheatCtx) -> Result<bool, Error> {
|
||||
let data: u8 = ctx.process.read(self.0 + cs2dumper::client::C_CSGameRules::m_bBombDropped)?;
|
||||
Ok(data != 0)
|
||||
}
|
||||
|
||||
pub fn total_rounds_played(&self, ctx: &mut CheatCtx) -> Result<i32, Error> {
|
||||
Ok(ctx.process.read(self.0 + cs2dumper::client::C_CSGameRules::m_totalRoundsPlayed)?)
|
||||
}
|
||||
|
||||
pub fn game_phase(&self, ctx: &mut CheatCtx) -> Result<i32, Error> {
|
||||
Ok(ctx.process.read(self.0 + cs2dumper::client::C_CSGameRules::m_gamePhase)?)
|
||||
}
|
||||
|
||||
pub fn bomb_planted(&self, ctx: &mut CheatCtx) -> Result<bool, Error> {
|
||||
let data: u8 = ctx.process.read(self.0 + cs2dumper::client::C_CSGameRules::m_bBombPlanted)?;
|
||||
Ok(data != 0)
|
||||
}
|
||||
}
|
67
csflow/src/structs/global_vars.rs
Normal file
67
csflow/src/structs/global_vars.rs
Normal file
@ -0,0 +1,67 @@
|
||||
use memflow::{types::Address, mem::MemoryView};
|
||||
use crate::{traits::MemoryClass, CheatCtx, Error};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct GlobalVars(Address);
|
||||
|
||||
impl MemoryClass for GlobalVars {
|
||||
fn ptr(&self) -> memflow::types::Address {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn new(ptr: memflow::types::Address) -> Self {
|
||||
Self(ptr)
|
||||
}
|
||||
}
|
||||
|
||||
impl GlobalVars {
|
||||
pub fn real_time(&self, ctx: &mut CheatCtx) -> Result<f32, Error> {
|
||||
Ok(ctx.process.read(self.0)?)
|
||||
}
|
||||
|
||||
pub fn frame_count(&self, ctx: &mut CheatCtx) -> Result<i32, Error> {
|
||||
Ok(ctx.process.read(self.0 + 0x4)?)
|
||||
}
|
||||
|
||||
pub fn frame_time(&self, ctx: &mut CheatCtx) -> Result<f32, Error> {
|
||||
Ok(ctx.process.read(self.0 + 0x8)?)
|
||||
}
|
||||
|
||||
pub fn absolute_frame_time(&self, ctx: &mut CheatCtx) -> Result<f32, Error> {
|
||||
Ok(ctx.process.read(self.0 + 0xC)?)
|
||||
}
|
||||
|
||||
pub fn max_clients(&self, ctx: &mut CheatCtx) -> Result<i32, Error> {
|
||||
Ok(ctx.process.read(self.0 + 0x10)?)
|
||||
}
|
||||
|
||||
pub fn tick_count(&self, ctx: &mut CheatCtx) -> Result<i32, Error> {
|
||||
Ok(ctx.process.read(self.0 + 0x40)?)
|
||||
}
|
||||
|
||||
pub fn map_name(&self, ctx: &mut CheatCtx) -> Result<String, Error> {
|
||||
let ptr = ctx.process.read_addr64(self.0 + 0x188)?;
|
||||
Ok(ctx.process.read_char_string_n(ptr, 32)?)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
struct GlobalVarsBase {
|
||||
real_time: f32, // 0x0000
|
||||
frame_count: i32, // 0x0004
|
||||
frame_time: f32, // 0x0008
|
||||
absolute_frame_time: f32, // 0x000C
|
||||
max_clients: i32, // 0x0010
|
||||
pad_0: [u8; 0x14], // 0x0014
|
||||
frame_time_2: f32, // 0x0028
|
||||
current_time: f32, // 0x002C
|
||||
current_time_2: f32, // 0x0030
|
||||
pad_1: [u8; 0xC], // 0x0034
|
||||
tick_count: f32, // 0x0040 // NO fucking idea why the fuck this "should" be an f32????
|
||||
pad_2: [u8; 0x4], // 0x0044
|
||||
network_channel: *const c_void, // 0x0048
|
||||
pad_3: [u8; 0x130], // 0x0050
|
||||
current_map: *const c_char, // 0x0180
|
||||
current_map_name: *const c_char, // 0x0188
|
||||
}
|
||||
*/
|
@ -1,5 +1,9 @@
|
||||
mod vec3;
|
||||
mod global_vars;
|
||||
mod gamerules;
|
||||
mod entity;
|
||||
|
||||
pub use vec3::Vec3;
|
||||
pub use global_vars::GlobalVars;
|
||||
pub use gamerules::GameRules;
|
||||
pub use entity::{CBaseEntity, CPlayerController, CPlayerPawn};
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "radarflow"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
authors = ["Janek S <development@superyu.xyz>"]
|
||||
edition = "2021"
|
||||
|
||||
|
@ -4,7 +4,6 @@ use clap::{Parser, ValueEnum};
|
||||
use csflow::{Connector, memflow::Inventory};
|
||||
|
||||
const PORT_RANGE: std::ops::RangeInclusive<usize> = 8000..=65535;
|
||||
const POLL_RANGE: std::ops::RangeInclusive<usize> = 1..=1000;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, version = version(), about, long_about = None)]
|
||||
@ -25,10 +24,6 @@ pub struct Cli {
|
||||
#[arg(short, long, default_value = "./webradar", value_parser = valid_path)]
|
||||
pub web_path: PathBuf,
|
||||
|
||||
/// Polling frequency in times per second for the DMA thread
|
||||
#[arg(short = 'r', long, default_value_t = 60, value_parser = poll_in_range)]
|
||||
pub poll_rate: u16,
|
||||
|
||||
/// Verbosity level for logging to the console
|
||||
#[arg(value_enum, long, short, ignore_case = true, default_value_t = Loglevel::Warn)]
|
||||
pub loglevel: Loglevel,
|
||||
@ -75,21 +70,6 @@ fn valid_path(s: &str) -> Result<PathBuf, String> {
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
fn poll_in_range(s: &str) -> Result<u16, String> {
|
||||
let port: usize = s
|
||||
.parse()
|
||||
.map_err(|_| format!("`{s}` isn't a valid number"))?;
|
||||
if POLL_RANGE.contains(&port) {
|
||||
Ok(port as u16)
|
||||
} else {
|
||||
Err(format!(
|
||||
"not in range {}-{}",
|
||||
POLL_RANGE.start(),
|
||||
POLL_RANGE.end()
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper because log::LevelFilter doesn't implement ValueEnum
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Default)]
|
||||
pub enum Loglevel {
|
||||
|
@ -1,4 +1,4 @@
|
||||
use csflow::{memflow::Address, enums::PlayerType};
|
||||
use csflow::{memflow::Address, enums::PlayerType, structs::{GlobalVars, GameRules}, traits::MemoryClass};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum CachedEntity {
|
||||
@ -8,54 +8,71 @@ pub enum CachedEntity {
|
||||
|
||||
pub struct Cache {
|
||||
timestamp: std::time::Instant,
|
||||
valid: bool,
|
||||
entity_cache: Vec<CachedEntity>,
|
||||
map_name: String,
|
||||
entity_list: Address,
|
||||
globals: GlobalVars,
|
||||
gamerules: GameRules,
|
||||
}
|
||||
|
||||
impl Cache {
|
||||
pub fn is_valid(&self) -> bool {
|
||||
if self.timestamp.elapsed() > std::time::Duration::from_millis(250) {
|
||||
return false;
|
||||
}
|
||||
if self.valid {
|
||||
if self.timestamp.elapsed() > std::time::Duration::from_secs(60 * 3) {
|
||||
log::info!("Invalidated cache! Reason: time");
|
||||
return false
|
||||
}
|
||||
|
||||
true
|
||||
true
|
||||
} else { false }
|
||||
}
|
||||
|
||||
pub fn new_invalid() -> Cache {
|
||||
Cache {
|
||||
timestamp: std::time::Instant::now().checked_sub(std::time::Duration::from_millis(500)).unwrap(),
|
||||
valid: false,
|
||||
entity_cache: Vec::new(),
|
||||
map_name: String::new(),
|
||||
entity_list: Address::null(),
|
||||
globals: GlobalVars::new(Address::null()),
|
||||
gamerules: GameRules::new(Address::null()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn invalidate(&mut self) {
|
||||
self.valid = false;
|
||||
}
|
||||
|
||||
pub fn entity_cache(&mut self) -> Vec<CachedEntity> {
|
||||
self.entity_cache.clone()
|
||||
}
|
||||
|
||||
pub fn map_name(&self) -> String {
|
||||
self.map_name.clone()
|
||||
}
|
||||
|
||||
pub fn entity_list(&self) -> Address {
|
||||
self.entity_list
|
||||
}
|
||||
|
||||
pub fn globals(&self) -> GlobalVars {
|
||||
self.globals
|
||||
}
|
||||
|
||||
pub fn gamerules(&self) -> GameRules {
|
||||
self.gamerules
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CacheBuilder {
|
||||
entity_cache: Option<Vec<CachedEntity>>,
|
||||
map_name: Option<String>,
|
||||
entity_list: Option<Address>
|
||||
entity_list: Option<Address>,
|
||||
globals: Option<GlobalVars>,
|
||||
gamerules: Option<GameRules>
|
||||
}
|
||||
|
||||
impl CacheBuilder {
|
||||
pub fn new() -> CacheBuilder {
|
||||
CacheBuilder {
|
||||
entity_cache: None,
|
||||
map_name: None,
|
||||
entity_list: None,
|
||||
globals: None,
|
||||
gamerules: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,22 +81,29 @@ impl CacheBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn map_name(mut self, map_name: String) -> CacheBuilder {
|
||||
self.map_name = Some(map_name);
|
||||
pub fn entity_list(mut self, entity_list: Address) -> CacheBuilder {
|
||||
self.entity_list = Some(entity_list);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn entity_list(mut self, entity_list: Address) -> CacheBuilder {
|
||||
self.entity_list = Some(entity_list);
|
||||
pub fn globals(mut self, globals: GlobalVars) -> CacheBuilder {
|
||||
self.globals = Some(globals);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn gamerules(mut self, gamerules: GameRules) -> CacheBuilder {
|
||||
self.gamerules = Some(gamerules);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> anyhow::Result<Cache> {
|
||||
Ok(Cache {
|
||||
timestamp: std::time::Instant::now(),
|
||||
valid: true,
|
||||
entity_cache: self.entity_cache.ok_or(anyhow::anyhow!("entity_cache not set on builder"))?,
|
||||
map_name: self.map_name.ok_or(anyhow::anyhow!("map_name not set on builder"))?,
|
||||
entity_list: self.entity_list.ok_or(anyhow::anyhow!("entity_list not set on builder"))?,
|
||||
globals: self.globals.ok_or(anyhow::anyhow!("globals not set on builder"))?,
|
||||
gamerules: self.gamerules.ok_or(anyhow::anyhow!("gamerules not set on builder"))?,
|
||||
})
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use csflow::{CheatCtx, Connector, memflow::Process, traits::{MemoryClass, BaseEntity}, enums::PlayerType, structs::{CBaseEntity, CPlayerController}};
|
||||
use tokio::{sync::RwLock, time::{Duration, Instant}};
|
||||
use tokio::{sync::RwLock, time::Duration};
|
||||
|
||||
use crate::{comms::{RadarData, EntityData, BombData, PlayerData}, dma::cache::CacheBuilder};
|
||||
|
||||
@ -10,30 +10,22 @@ use self::cache::Cache;
|
||||
mod cache;
|
||||
|
||||
const SECOND_AS_NANO: u64 = 1000*1000*1000;
|
||||
static ONCE: std::sync::Once = std::sync::Once::new();
|
||||
|
||||
pub async fn run(connector: Connector, pcileech_device: String, poll_rate: u16, data_lock: Arc<RwLock<RadarData>>) -> anyhow::Result<()> {
|
||||
pub async fn run(connector: Connector, pcileech_device: String, data_lock: Arc<RwLock<RadarData>>) -> anyhow::Result<()> {
|
||||
let mut ctx = CheatCtx::setup(connector, pcileech_device)?;
|
||||
|
||||
println!("---------------------------------------------------");
|
||||
println!("Found cs2.exe at {:X}", ctx.process.info().address);
|
||||
println!("Found engine module at cs2.exe+{:X}", ctx.engine_module.base);
|
||||
println!("Found client module at cs2.exe+{:X}", ctx.client_module.base);
|
||||
println!("---------------------------------------------------");
|
||||
|
||||
// Avoid printing warnings and other stuff before the initial prints are complete
|
||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||
|
||||
// For poll rate timing
|
||||
let should_time = poll_rate != 0;
|
||||
|
||||
let target_interval = Duration::from_nanos(SECOND_AS_NANO / poll_rate as u64);
|
||||
let mut last_iteration_time = Instant::now();
|
||||
let mut missmatch_count = 0;
|
||||
|
||||
let mut cache = Cache::new_invalid();
|
||||
|
||||
let mut last_tickcount = -1;
|
||||
let mut last_round = -1;
|
||||
let mut last_gamephase = -1;
|
||||
|
||||
// Duration for a single tick on 128 ticks. Im assuming 128 ticks because I dont fucking know how to read the current tickrate off cs2 memory lol
|
||||
let target_interval = Duration::from_nanos(SECOND_AS_NANO / 128);
|
||||
|
||||
loop {
|
||||
let start_stamp = tokio::time::Instant::now();
|
||||
|
||||
if ctx.process.state().is_dead() {
|
||||
break;
|
||||
}
|
||||
@ -43,8 +35,8 @@ pub async fn run(connector: Connector, pcileech_device: String, poll_rate: u16,
|
||||
|
||||
let globals = ctx.get_globals()?;
|
||||
let highest_index = ctx.highest_entity_index()?;
|
||||
let map_name = ctx.map_name(globals)?;
|
||||
let entity_list = ctx.get_entity_list()?;
|
||||
let gamerules = ctx.get_gamerules()?;
|
||||
|
||||
let local = ctx.get_local()?;
|
||||
|
||||
@ -87,116 +79,147 @@ pub async fn run(connector: Connector, pcileech_device: String, poll_rate: u16,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
cache = CacheBuilder::new()
|
||||
.entity_cache(cached_entities)
|
||||
.entity_list(entity_list)
|
||||
.map_name(map_name)
|
||||
.globals(globals)
|
||||
.gamerules(gamerules)
|
||||
.build()?;
|
||||
|
||||
log::debug!("Rebuilt cache.");
|
||||
log::info!("Rebuilt cache.");
|
||||
}
|
||||
|
||||
if ctx.network_is_ingame()? {
|
||||
let mut radar_data = Vec::with_capacity(64);
|
||||
// Check if mapname is "<empty>"
|
||||
// That means we are not in-game, so we can just write empty radar data and run the next loop.
|
||||
let map_name = cache.globals().map_name(&mut ctx)?;
|
||||
|
||||
if ctx.is_bomb_planted()? {
|
||||
let bomb = ctx.get_plantedc4()?;
|
||||
let bomb_pos = bomb.pos(&mut ctx)?;
|
||||
radar_data.push(
|
||||
EntityData::Bomb(BombData::new(
|
||||
bomb_pos,
|
||||
true
|
||||
))
|
||||
);
|
||||
if map_name == "<empty>" {
|
||||
last_round = -1;
|
||||
last_gamephase = -1;
|
||||
|
||||
let mut data = data_lock.write().await;
|
||||
*data = RadarData::empty();
|
||||
continue;
|
||||
} else if map_name.is_empty() { // Check if mapname is empty, this usually means a bad globals pointer -> rebuild our cache
|
||||
cache.invalidate();
|
||||
log::info!("Invalidated cache! Reason: invalid globals pointer");
|
||||
continue;
|
||||
}
|
||||
|
||||
for cached_data in cache.entity_cache() {
|
||||
match cached_data {
|
||||
cache::CachedEntity::Bomb { ptr } => {
|
||||
if ctx.is_bomb_dropped()? {
|
||||
let bomb_entity = CBaseEntity::new(ptr);
|
||||
let pos = bomb_entity.pos(&mut ctx)?;
|
||||
let cur_round = cache.gamerules().total_rounds_played(&mut ctx)?;
|
||||
|
||||
// New round started, invalidate cache and run next loop
|
||||
if cur_round != last_round {
|
||||
last_round = cur_round;
|
||||
cache.invalidate();
|
||||
log::info!("Invalidated cache! Reason: new round");
|
||||
continue;
|
||||
}
|
||||
|
||||
let cur_gamephase = cache.gamerules().game_phase(&mut ctx)?;
|
||||
|
||||
// New game phase, invalidate cache and run next loop
|
||||
if cur_gamephase != last_gamephase {
|
||||
last_gamephase = cur_gamephase;
|
||||
cache.invalidate();
|
||||
log::info!("Invalidated cache! Reason: new gamephase");
|
||||
continue;
|
||||
}
|
||||
|
||||
let cur_tickcount = cache.globals().tick_count(&mut ctx)?;
|
||||
|
||||
// New tick, now we want to fetch our data
|
||||
if cur_tickcount != last_tickcount {
|
||||
// We dont expect more than 16 entries in our radar data
|
||||
let mut radar_data = Vec::with_capacity(16);
|
||||
|
||||
if cache.gamerules().bomb_planted(&mut ctx)? {
|
||||
let bomb = ctx.get_plantedc4()?;
|
||||
let bomb_pos = bomb.pos(&mut ctx)?;
|
||||
radar_data.push(
|
||||
EntityData::Bomb(BombData::new(
|
||||
bomb_pos,
|
||||
true
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
radar_data.push(
|
||||
EntityData::Bomb(
|
||||
BombData::new(
|
||||
pos,
|
||||
false
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
cache::CachedEntity::Player { ptr, player_type } => {
|
||||
let controller = CPlayerController::new(ptr);
|
||||
if let Some(pawn) = controller.get_pawn(&mut ctx, cache.entity_list())? {
|
||||
if pawn.is_alive(&mut ctx)? {
|
||||
let pos = pawn.pos(&mut ctx)?;
|
||||
let yaw = pawn.angles(&mut ctx)?.y;
|
||||
let has_bomb = pawn.has_c4(&mut ctx, cache.entity_list())?;
|
||||
|
||||
for cached_data in cache.entity_cache() {
|
||||
match cached_data {
|
||||
cache::CachedEntity::Bomb { ptr } => {
|
||||
if cache.gamerules().bomb_dropped(&mut ctx)? {
|
||||
let bomb_entity = CBaseEntity::new(ptr);
|
||||
let pos = bomb_entity.pos(&mut ctx)?;
|
||||
|
||||
radar_data.push(
|
||||
EntityData::Player(
|
||||
PlayerData::new(
|
||||
pos,
|
||||
yaw,
|
||||
player_type,
|
||||
has_bomb
|
||||
EntityData::Bomb(
|
||||
BombData::new(
|
||||
pos,
|
||||
false
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let mut data = data_lock.write().await;
|
||||
if cache.map_name() == "<empty>" || cache.map_name().is_empty() {
|
||||
*data = RadarData::empty()
|
||||
} else {
|
||||
*data = RadarData::new(true, cache.map_name(), radar_data)
|
||||
}
|
||||
} else {
|
||||
let mut data = data_lock.write().await;
|
||||
*data = RadarData::empty()
|
||||
}
|
||||
|
||||
if should_time {
|
||||
let elapsed = last_iteration_time.elapsed();
|
||||
|
||||
let remaining = match target_interval.checked_sub(elapsed) {
|
||||
Some(t) => t,
|
||||
None => {
|
||||
if missmatch_count >= 25 {
|
||||
ONCE.call_once(|| {
|
||||
log::warn!("Remaining time till target interval was negative more than 25 times");
|
||||
log::warn!("You should decrease your poll rate.");
|
||||
log::warn!("elapsed: {}ns", elapsed.as_nanos());
|
||||
log::warn!("target: {}ns", target_interval.as_nanos());
|
||||
});
|
||||
} else {
|
||||
missmatch_count += 1;
|
||||
},
|
||||
cache::CachedEntity::Player { ptr, player_type } => {
|
||||
let controller = CPlayerController::new(ptr);
|
||||
if let Some(pawn) = controller.get_pawn(&mut ctx, cache.entity_list())? {
|
||||
if pawn.is_alive(&mut ctx)? {
|
||||
let pos = pawn.pos(&mut ctx)?;
|
||||
let yaw = pawn.angles(&mut ctx)?.y;
|
||||
let has_bomb = pawn.has_c4(&mut ctx, cache.entity_list())?;
|
||||
|
||||
radar_data.push(
|
||||
EntityData::Player(
|
||||
PlayerData::new(
|
||||
pos,
|
||||
yaw,
|
||||
player_type,
|
||||
has_bomb
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
Duration::from_nanos(0)
|
||||
},
|
||||
};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
tokio_timerfd::sleep(remaining).await?;
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
tokio::time::sleep(remaining).await;
|
||||
|
||||
log::info!("poll rate: {:.2}Hz", SECOND_AS_NANO as f64 / last_iteration_time.elapsed().as_nanos() as f64);
|
||||
log::trace!("elapsed: {}ns", elapsed.as_nanos());
|
||||
log::trace!("target: {}ns", target_interval.as_nanos());
|
||||
log::trace!("missmatch count: {}", missmatch_count);
|
||||
}
|
||||
|
||||
last_iteration_time = Instant::now();
|
||||
let mut data = data_lock.write().await;
|
||||
*data = RadarData::new(
|
||||
true,
|
||||
map_name,
|
||||
radar_data
|
||||
);
|
||||
|
||||
last_tickcount = cur_tickcount;
|
||||
}
|
||||
}
|
||||
|
||||
// Elapsed time since we started our loop
|
||||
let elapsed = start_stamp.elapsed();
|
||||
|
||||
let remaining = match target_interval.checked_sub(elapsed) {
|
||||
// This gives us the remaining time we can sleep in our loop
|
||||
Some(t) => t,
|
||||
// No time left, start next loop.
|
||||
None => continue
|
||||
};
|
||||
|
||||
// On linux we may use tokio_timerfd for a more finely grained sleep function
|
||||
#[cfg(target_os = "linux")]
|
||||
tokio_timerfd::sleep(remaining).await?;
|
||||
|
||||
// On non linux build targets we need to use the regular sleep function, this one is only accurate to millisecond precision
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
tokio::time::sleep(remaining).await;
|
||||
|
||||
log::debug!("poll rate: {:.2}Hz", SECOND_AS_NANO as f64 / start_stamp.elapsed().as_nanos() as f64);
|
||||
log::debug!("elapsed: {}ns", elapsed.as_nanos());
|
||||
log::debug!("target: {}ns", target_interval.as_nanos());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -28,7 +28,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
|
||||
let rwlock_clone = rwlock.clone();
|
||||
let dma_handle = tokio::spawn(async move {
|
||||
if let Err(err) = dma::run(cli.connector, cli.pcileech_device, cli.poll_rate, rwlock_clone).await {
|
||||
if let Err(err) = dma::run(cli.connector, cli.pcileech_device, rwlock_clone).await {
|
||||
log::error!("Error in dma thread: [{}]", err.to_string());
|
||||
}
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user