optimize (not done yet)

- alpha-alpha implementation of CachedView in csflow.

- MUCH faster entity cache build time thanks to two professionally engineered functions 😎
This commit is contained in:
Janek
2024-01-05 00:31:11 +01:00
parent 50677fafef
commit f186b19255
14 changed files with 417 additions and 129 deletions

View File

@@ -9,8 +9,8 @@ description = "SDK for CS2 cheats utilizing memflow"
[dependencies]
# memory
memflow = "0.2.0"
dataview = "1.0.1"
memflow = { git = "https://github.com/memflow/memflow.git", rev = "c19d870" }
# logging
log = "0.4.19"

View File

@@ -0,0 +1,122 @@
use ::std::sync::{atomic::{AtomicU8, Ordering, AtomicI32}, Arc};
use memflow::prelude::v1::*;
pub const INVALIDATE_ALWAYS: u8 = 0b00000001;
pub const INVALIDATE_TICK: u8 = 0b00000010;
pub struct ExternallyControlledValidator {
validator_next_flags: Arc<AtomicU8>,
validator_tick_count: Arc<AtomicI32>,
}
impl ExternallyControlledValidator {
pub fn new() -> Self {
Self {
validator_next_flags: Arc::new(AtomicU8::new(INVALIDATE_ALWAYS)),
validator_tick_count: Arc::new(AtomicI32::new(0)),
}
}
pub fn set_next_flags(&mut self, flags: u8) {
self.validator_next_flags
.store(flags as u8, Ordering::SeqCst);
}
pub fn set_tick_count(&mut self, tick_count: i32) {
self.validator_tick_count
.store(tick_count, Ordering::SeqCst);
}
pub fn validator(&self) -> CustomValidator {
CustomValidator::new(
self.validator_next_flags.clone(),
self.validator_tick_count.clone(),
)
}
}
#[derive(Clone)]
struct ValidatorSlot {
value: i32,
flags: u8,
}
#[derive(Clone)]
pub struct CustomValidator {
slots: Vec<ValidatorSlot>,
// The invalidation flags used for the next read or write.
next_flags: Arc<AtomicU8>,
next_flags_local: u8,
// last_count is used to quickly invalidate slots without having to
// iterate over all slots and invalidating manually.
last_count: usize,
// tick count is the externally controlled tick number that will
// invalidate specific caches when it is increased.
tick_count: Arc<AtomicI32>,
tick_count_local: i32,
}
impl CustomValidator {
pub fn new(next_flags: Arc<AtomicU8>, tick_count: Arc<AtomicI32>) -> Self {
Self {
slots: vec![],
next_flags,
next_flags_local: INVALIDATE_ALWAYS,
last_count: 0,
tick_count,
tick_count_local: -1,
}
}
}
impl CacheValidator for CustomValidator {
// Create a vector containing all slots with a predefined invalid state.
fn allocate_slots(&mut self, slot_count: usize) {
self.slots.resize(
slot_count,
ValidatorSlot {
value: -1,
flags: INVALIDATE_ALWAYS,
},
);
}
// This function is invoked on every batch of memory operations.
// This simply updates the internal state and reads the Atomic variables for the upcoming validations.
fn update_validity(&mut self) {
self.last_count = self.last_count.wrapping_add(1);
self.next_flags_local = self.next_flags.load(Ordering::SeqCst);
self.tick_count_local = self.tick_count.load(Ordering::SeqCst);
}
// This simply returns true or false if the slot is valid or not.
// `last_count` is used here to invalidate slots quickly without requiring to iterate over the entire slot list.
fn is_slot_valid(&self, slot_id: usize) -> bool {
match self.slots[slot_id].flags {
INVALIDATE_ALWAYS => self.slots[slot_id].value == self.last_count as i32,
INVALIDATE_TICK => self.slots[slot_id].value == self.tick_count_local as i32,
_ => false,
}
}
// In case the cache is being updates this function marks the slot as being valid.
fn validate_slot(&mut self, slot_id: usize) {
match self.next_flags_local {
INVALIDATE_ALWAYS => self.slots[slot_id].value = self.last_count as i32,
INVALIDATE_TICK => self.slots[slot_id].value = self.tick_count_local as i32,
_ => (),
}
self.slots[slot_id].flags = self.next_flags_local;
}
// In case a slot has to be freed this function resets it to the default values.
fn invalidate_slot(&mut self, slot_id: usize) {
self.slots[slot_id].value = -1;
self.slots[slot_id].flags = INVALIDATE_ALWAYS;
}
}

View File

@@ -1,16 +1,23 @@
use memflow::{plugins::{IntoProcessInstanceArcBox, Inventory, ConnectorArgs, args::Args}, os::{ModuleInfo, Os, Process}, mem::MemoryView, types::Address};
use memflow::{plugins::{IntoProcessInstanceArcBox, Inventory, ConnectorArgs, args::Args}, os::{ModuleInfo, Os, Process}, mem::{MemoryView, CachedView}, types::Address};
use memflow::prelude::v1::size;
use crate::{error::Error, structs::{CPlayerController, CBaseEntity, GlobalVars, GameRules}, cs2dumper, traits::MemoryClass};
use self::cached_view::{ExternallyControlledValidator, CustomValidator, INVALIDATE_TICK};
pub mod cached_view;
pub struct CheatCtx {
pub process: IntoProcessInstanceArcBox<'static>,
pub memory: CachedView<'static, IntoProcessInstanceArcBox<'static>, CustomValidator>,
pub cache_controller: ExternallyControlledValidator,
pub client_module: ModuleInfo,
pub engine_module: ModuleInfo,
}
impl CheatCtx {
fn check_version(&mut self) -> Result<(), Error> {
let game_build_number: u32 = self.process.read(self.engine_module.base + cs2dumper::offsets::engine2_dll::dwBuildNumber)?;
let game_build_number: u32 = self.memory.read(self.engine_module.base + cs2dumper::offsets::engine2_dll::dwBuildNumber)?;
let offset_build_number = cs2dumper::offsets::game_info::buildNumber;
if game_build_number as usize != offset_build_number {
@@ -49,8 +56,24 @@ impl CheatCtx {
let engine_module = process.module_by_name("engine2.dll")?;
// Create the validator
let mut validator_controller = ExternallyControlledValidator::new();
let validator = validator_controller.validator();
// Create CachedView over the processes MemoryView
let proc_arch = process.info().proc_arch;
let cached_process = CachedView::builder(process.clone())
.arch(proc_arch)
.validator(validator)
.cache_size(size::mb(10))
.build()?;
validator_controller.set_next_flags(INVALIDATE_TICK);
let mut ctx = Self {
process,
process: process,
memory: cached_process,
cache_controller: validator_controller,
client_module,
engine_module,
};
@@ -61,29 +84,29 @@ impl CheatCtx {
}
pub fn get_local(&mut self) -> Result<CPlayerController, Error> {
let ptr = self.process.read_addr64(self.client_module.base + cs2dumper::offsets::client_dll::dwLocalPlayerController)?;
let ptr = self.memory.read_addr64(self.client_module.base + cs2dumper::offsets::client_dll::dwLocalPlayerController)?;
Ok(CPlayerController::new(ptr))
}
pub fn get_plantedc4(&mut self) -> Result<CBaseEntity, Error> {
let ptr = self.process.read_addr64(self.client_module.base + cs2dumper::offsets::client_dll::dwPlantedC4)?;
let ptr2 = self.process.read_addr64(ptr)?;
let ptr = self.memory.read_addr64(self.client_module.base + cs2dumper::offsets::client_dll::dwPlantedC4)?;
let ptr2 = self.memory.read_addr64(ptr)?;
Ok(CBaseEntity::new(ptr2))
}
pub fn get_globals(&mut self) -> Result<GlobalVars, Error> {
let ptr = self.process.read_addr64(self.client_module.base + cs2dumper::offsets::client_dll::dwGlobalVars)?;
let ptr = self.memory.read_addr64(self.client_module.base + cs2dumper::offsets::client_dll::dwGlobalVars)?;
Ok(GlobalVars::new(ptr))
}
pub fn get_gamerules(&mut self) -> Result<GameRules, Error> {
let ptr = self.process.read_addr64(self.client_module.base + cs2dumper::offsets::client_dll::dwGameRules)?;
let ptr = self.memory.read_addr64(self.client_module.base + cs2dumper::offsets::client_dll::dwGameRules)?;
Ok(GameRules::new(ptr))
}
// todo: separate into own class
pub fn get_entity_list(&mut self) -> Result<Address, Error> {
let ptr = self.process.read_addr64(self.client_module.base + cs2dumper::offsets::client_dll::dwEntityList)?;
let ptr = self.memory.read_addr64(self.client_module.base + cs2dumper::offsets::client_dll::dwEntityList)?;
Ok(ptr)
}

View File

@@ -32,5 +32,8 @@ pub enum Error {
MemflowPartialf32(#[from] memflow::error::PartialError<f32>),
#[error("memflow partial error when reading u8: {0}")]
MemflowPartialu8(#[from] memflow::error::PartialError<u8>)
MemflowPartialu8(#[from] memflow::error::PartialError<u8>),
#[error("memflow partial error when reading Vec<u8>: {0}")]
MemflowPartialVecu8(#[from] memflow::error::PartialError<Vec<u8>>)
}

View File

@@ -16,12 +16,12 @@ impl MemoryClass for CBaseEntity {
impl BaseEntity for CBaseEntity {
fn from_index(ctx: &mut CheatCtx, entity_list: Address, index: i32) -> Result<Option<CBaseEntity>, Error> {
let list_entry = ctx.process.read_addr64(entity_list + 8 * (index >> 9) + 16)?;
let list_entry = ctx.memory.read_addr64(entity_list + 8 * (index >> 9) + 16)?;
if list_entry.is_null() && !list_entry.is_valid() {
return Ok(None);
}
let player_ptr = ctx.process.read_addr64(list_entry + 120 * (index & 0x1FF))?;
let player_ptr = ctx.memory.read_addr64(list_entry + 120 * (index & 0x1FF))?;
if player_ptr.is_null() && !player_ptr.is_valid() {
return Ok(None);
}
@@ -30,14 +30,28 @@ impl BaseEntity for CBaseEntity {
}
fn pos(&self, ctx: &mut CheatCtx) -> Result<Vec3, Error> {
let node = ctx.process.read_addr64(self.0 + cs2dumper::client::C_BaseEntity::m_pGameSceneNode)?;
Ok(ctx.process.read(node + cs2dumper::client::CGameSceneNode::m_vecAbsOrigin)?)
let node = ctx.memory.read_addr64(self.0 + cs2dumper::client::C_BaseEntity::m_pGameSceneNode)?;
Ok(ctx.memory.read(node + cs2dumper::client::CGameSceneNode::m_vecAbsOrigin)?)
}
fn class_name(&self, ctx: &mut CheatCtx) -> Result<String, Error> {
let entity_identity_ptr = ctx.process.read_addr64(self.0 + cs2dumper::client::CEntityInstance::m_pEntity)?;
let class_name_ptr = ctx.process.read_addr64(entity_identity_ptr + cs2dumper::client::CEntityIdentity::m_designerName)?;
Ok(ctx.process.read_char_string_n(class_name_ptr, 32)?)
Ok(ctx.memory.read_char_string_n(class_name_ptr, 32)?)
}
fn next_by_class(&self, ctx: &mut CheatCtx) -> Result<Self, Error> where Self: std::marker::Sized {
let entity_identity_ptr = ctx.process.read_addr64(self.0 + cs2dumper::client::CEntityInstance::m_pEntity)?;
let next_by_class_ptr = ctx.process.read_addr64(entity_identity_ptr + cs2dumper::client::CEntityIdentity::m_pNextByClass)?;
let entity_ptr = ctx.process.read_addr64(next_by_class_ptr)?;
Ok(Self(entity_ptr))
}
fn previous_by_class(&self, ctx: &mut CheatCtx) -> Result<Self, Error> where Self: std::marker::Sized {
let entity_identity_ptr = ctx.memory.read_addr64(self.0 + cs2dumper::client::CEntityInstance::m_pEntity)?;
let next_by_class_ptr = ctx.process.read_addr64(entity_identity_ptr + cs2dumper::client::CEntityIdentity::m_pPrevByClass)?;
let entity_ptr = ctx.process.read_addr64(next_by_class_ptr)?;
Ok(Self(entity_ptr))
}
}
@@ -45,4 +59,24 @@ impl CBaseEntity {
pub fn to_player_controller(&self) -> super::CPlayerController {
super::CPlayerController::new(self.0)
}
/// Professionally engineered function to quickly check if the entity has class name "weapon_c4"
pub fn is_dropped_c4(&self, ctx: &mut CheatCtx) -> Result<bool, Error> {
let entity_identity_ptr = ctx.process.read_addr64(self.0 + cs2dumper::client::CEntityInstance::m_pEntity)?;
let class_name_ptr = ctx.process.read_addr64(entity_identity_ptr + cs2dumper::client::CEntityIdentity::m_designerName)?;
let data = ctx.process.read_raw(class_name_ptr + 7, 2)?;
let is_c4 = data == "c4".as_bytes();
Ok(is_c4)
}
/// Professionally engineered function to quickly check if the entity has class name "cs_player_controller"
pub fn is_cs_player_controller(&self, ctx: &mut CheatCtx) -> Result<bool, Error> {
let entity_identity_ptr = ctx.process.read_addr64(self.0 + cs2dumper::client::CEntityInstance::m_pEntity)?;
let class_name_ptr = ctx.process.read_addr64(entity_identity_ptr + cs2dumper::client::CEntityIdentity::m_designerName)?;
let data = ctx.process.read_raw(class_name_ptr, 20)?;
let is_controller = data == "cs_player_controller".as_bytes();
Ok(is_controller)
}
}

View File

@@ -18,12 +18,12 @@ impl MemoryClass for CPlayerController {
impl BaseEntity for CPlayerController {
fn from_index(ctx: &mut CheatCtx, entity_list: Address, index: i32) -> Result<Option<CPlayerController>, Error> {
let list_entry = ctx.process.read_addr64(entity_list + 8 * (index >> 9) + 16)?;
let list_entry = ctx.memory.read_addr64(entity_list + 8 * (index >> 9) + 16)?;
if list_entry.is_null() && !list_entry.is_valid() {
return Ok(None);
}
let player_ptr = ctx.process.read_addr64(list_entry + 120 * (index & 0x1FF))?;
let player_ptr = ctx.memory.read_addr64(list_entry + 120 * (index & 0x1FF))?;
if player_ptr.is_null() && !player_ptr.is_valid() {
return Ok(None);
}
@@ -31,27 +31,52 @@ impl BaseEntity for CPlayerController {
Ok(Some(Self::new(player_ptr)))
}
/// This function is slower because of an additional pointer read, use pawn.pos() instead.
fn pos(&self, ctx: &mut CheatCtx) -> Result<Vec3, Error> {
let node = ctx.process.read_addr64(self.0 + cs2dumper::client::C_BaseEntity::m_pGameSceneNode)?;
Ok(ctx.process.read(node + cs2dumper::client::CGameSceneNode::m_vecAbsOrigin)?)
let node = ctx.memory.read_addr64(self.0 + cs2dumper::client::C_BaseEntity::m_pGameSceneNode)?;
Ok(ctx.memory.read(node + cs2dumper::client::CGameSceneNode::m_vecAbsOrigin)?)
}
fn class_name(&self, ctx: &mut CheatCtx) -> Result<String, Error> {
let entity_identity_ptr = ctx.process.read_addr64(self.0 + cs2dumper::client::CEntityInstance::m_pEntity)?;
let class_name_ptr = ctx.process.read_addr64(entity_identity_ptr + cs2dumper::client::CEntityIdentity::m_designerName)?;
Ok(ctx.process.read_char_string_n(class_name_ptr, 32)?)
Ok(ctx.memory.read_char_string_n(class_name_ptr, 32)?)
}
fn next_by_class(&self, ctx: &mut CheatCtx) -> Result<Self, Error> where Self: std::marker::Sized {
let entity_identity_ptr = ctx.process.read_addr64(self.0 + cs2dumper::client::CEntityInstance::m_pEntity)?;
let next_by_class_ptr = ctx.process.read_addr64(entity_identity_ptr + cs2dumper::client::CEntityIdentity::m_pNextByClass)?;
let class_name_ptr = ctx.process.read_addr64(entity_identity_ptr + cs2dumper::client::CEntityIdentity::m_designerName)?;
let class_name= ctx.process.read_char_string_n(class_name_ptr, 32)?;
println!("class_name: {}", class_name);
let stringable_index: i32 = ctx.process.read(entity_identity_ptr + cs2dumper::client::CEntityIdentity::m_nameStringableIndex)?;
println!("stringable_index: {}", stringable_index);
let entity_ptr = ctx.process.read_addr64(next_by_class_ptr)?;
println!("entity_ptr: {}", entity_ptr);
Ok(Self(entity_ptr))
}
fn previous_by_class(&self, ctx: &mut CheatCtx) -> Result<Self, Error> where Self: std::marker::Sized {
let entity_identity_ptr = ctx.process.read_addr64(self.0 + cs2dumper::client::CEntityInstance::m_pEntity)?;
let next_by_class_ptr = ctx.process.read_addr64(entity_identity_ptr + cs2dumper::client::CEntityIdentity::m_pPrevByClass)?;
let entity_ptr = ctx.process.read_addr64(next_by_class_ptr)?;
Ok(Self(entity_ptr))
}
}
impl CPlayerController {
pub fn get_pawn(&self, ctx: &mut CheatCtx, entity_list: Address) -> Result<Option<super::CPlayerPawn>, Error> {
let uhandle = ctx.process.read(self.0 + cs2dumper::client::CCSPlayerController::m_hPlayerPawn)?;
let uhandle = ctx.memory.read(self.0 + cs2dumper::client::CCSPlayerController::m_hPlayerPawn)?;
super::CPlayerPawn::from_uhandle(ctx, entity_list, uhandle)
}
// Technically something that should be in the BaseEntity trait, but we are only gonna use it on CPlayerController
pub fn get_team(&self, ctx: &mut CheatCtx) -> Result<Option<TeamID>, Error> {
let team_num: i32 = ctx.process.read(self.0 + cs2dumper::client::C_BaseEntity::m_iTeamNum)?;
let team_num: i32 = ctx.memory.read(self.0 + cs2dumper::client::C_BaseEntity::m_iTeamNum)?;
Ok(TeamID::from_i32(team_num))
}

View File

@@ -17,12 +17,12 @@ impl MemoryClass for CPlayerPawn {
impl CPlayerPawn {
pub fn from_uhandle(ctx: &mut CheatCtx, entity_list: Address, uhandle: u32) -> Result<Option<Self>, Error> {
let list_entry = ctx.process.read_addr64(entity_list + 0x8 * ((uhandle & 0x7FFF) >> 9) + 16)?;
let list_entry = ctx.memory.read_addr64(entity_list + 0x8 * ((uhandle & 0x7FFF) >> 9) + 16)?;
if list_entry.is_null() || !list_entry.is_valid() {
Ok(None)
} else {
let ptr = ctx.process.read_addr64(list_entry + 120 * (uhandle & 0x1FF))?;
let ptr = ctx.memory.read_addr64(list_entry + 120 * (uhandle & 0x1FF))?;
Ok(Some(Self(ptr)))
}
}
@@ -30,27 +30,27 @@ impl CPlayerPawn {
// Todo: Optimize this function: find another way to do this
pub fn has_c4(&self, ctx: &mut CheatCtx, entity_list: Address) -> Result<bool, Error> {
let mut has_c4 = false;
let wep_services = ctx.process.read_addr64(self.0 + cs2dumper::client::C_BasePlayerPawn::m_pWeaponServices)?;
let wep_count: i32 = ctx.process.read(wep_services + cs2dumper::client::CPlayer_WeaponServices::m_hMyWeapons)?;
let wep_base = ctx.process.read_addr64(wep_services + cs2dumper::client::CPlayer_WeaponServices::m_hMyWeapons + 0x8)?;
let wep_services = ctx.memory.read_addr64(self.0 + cs2dumper::client::C_BasePlayerPawn::m_pWeaponServices)?;
let wep_count: i32 = ctx.memory.read(wep_services + cs2dumper::client::CPlayer_WeaponServices::m_hMyWeapons)?;
let wep_base = ctx.memory.read_addr64(wep_services + cs2dumper::client::CPlayer_WeaponServices::m_hMyWeapons + 0x8)?;
for wep_idx in 0..wep_count {
let handle: i32 = ctx.process.read(wep_base + wep_idx * 0x4)?;
let handle: i32 = ctx.memory.read(wep_base + wep_idx * 0x4)?;
if handle == -1 {
continue;
}
let list_entry = ctx.process.read_addr64(entity_list + 0x8 * ((handle & 0x7FFF) >> 9) + 16)?;
let list_entry = ctx.memory.read_addr64(entity_list + 0x8 * ((handle & 0x7FFF) >> 9) + 16)?;
if let Some(wep_ptr) = {
if list_entry.is_null() || !list_entry.is_valid() {
None
} else {
let ptr = ctx.process.read_addr64(list_entry + 120 * (handle & 0x1FF))?;
let ptr = ctx.memory.read_addr64(list_entry + 120 * (handle & 0x1FF))?;
Some(ptr)
}
} {
let wep_data = ctx.process.read_addr64(wep_ptr + cs2dumper::client::C_BaseEntity::m_nSubclassID + 0x8)?;
let id: i32 = ctx.process.read(wep_data + cs2dumper::client::CCSWeaponBaseVData::m_WeaponType)?;
let wep_data = ctx.memory.read_addr64(wep_ptr + cs2dumper::client::C_BaseEntity::m_nSubclassID + 0x8)?;
let id: i32 = ctx.memory.read(wep_data + cs2dumper::client::CCSWeaponBaseVData::m_WeaponType)?;
if id == 7 {
has_c4 = true;
@@ -63,15 +63,15 @@ impl CPlayerPawn {
}
pub fn pos(&self, ctx: &mut CheatCtx) -> Result<Vec3, Error> {
Ok(ctx.process.read(self.0 + cs2dumper::client::C_BasePlayerPawn::m_vOldOrigin)?)
Ok(ctx.memory.read(self.0 + cs2dumper::client::C_BasePlayerPawn::m_vOldOrigin)?)
}
pub fn angles(&self, ctx: &mut CheatCtx) -> Result<Vec3, Error> {
Ok(ctx.process.read(self.0 + cs2dumper::client::C_CSPlayerPawnBase::m_angEyeAngles)?)
Ok(ctx.memory.read(self.0 + cs2dumper::client::C_CSPlayerPawnBase::m_angEyeAngles)?)
}
pub fn health(&self, ctx: &mut CheatCtx) -> Result<u32, Error> {
Ok(ctx.process.read(self.0 + cs2dumper::client::C_BaseEntity::m_iHealth)?)
Ok(ctx.memory.read(self.0 + cs2dumper::client::C_BaseEntity::m_iHealth)?)
}
/// Same as ::get_health > 0

View File

@@ -16,20 +16,20 @@ impl MemoryClass for GameRules {
impl GameRules {
pub fn bomb_dropped(&self, ctx: &mut CheatCtx) -> Result<bool, Error> {
let data: u8 = ctx.process.read(self.0 + cs2dumper::client::C_CSGameRules::m_bBombDropped)?;
let data: u8 = ctx.memory.read(self.0 + cs2dumper::client::C_CSGameRules::m_bBombDropped)?;
Ok(data != 0)
}
pub fn total_rounds_played(&self, ctx: &mut CheatCtx) -> Result<i32, Error> {
Ok(ctx.process.read(self.0 + cs2dumper::client::C_CSGameRules::m_totalRoundsPlayed)?)
Ok(ctx.memory.read(self.0 + cs2dumper::client::C_CSGameRules::m_totalRoundsPlayed)?)
}
pub fn game_phase(&self, ctx: &mut CheatCtx) -> Result<i32, Error> {
Ok(ctx.process.read(self.0 + cs2dumper::client::C_CSGameRules::m_gamePhase)?)
Ok(ctx.memory.read(self.0 + cs2dumper::client::C_CSGameRules::m_gamePhase)?)
}
pub fn bomb_planted(&self, ctx: &mut CheatCtx) -> Result<bool, Error> {
let data: u8 = ctx.process.read(self.0 + cs2dumper::client::C_CSGameRules::m_bBombPlanted)?;
let data: u8 = ctx.memory.read(self.0 + cs2dumper::client::C_CSGameRules::m_bBombPlanted)?;
Ok(data != 0)
}
}

View File

@@ -1,5 +1,5 @@
use memflow::{types::Address, mem::MemoryView};
use crate::{traits::MemoryClass, CheatCtx, Error};
use crate::{traits::MemoryClass, CheatCtx, Error, cached_view::INVALIDATE_ALWAYS};
#[derive(Debug, Clone, Copy)]
pub struct GlobalVars(Address);
@@ -16,6 +16,7 @@ impl MemoryClass for GlobalVars {
impl GlobalVars {
pub fn real_time(&self, ctx: &mut CheatCtx) -> Result<f32, Error> {
ctx.cache_controller.set_next_flags(INVALIDATE_ALWAYS);
Ok(ctx.process.read(self.0)?)
}
@@ -40,7 +41,7 @@ impl GlobalVars {
}
pub fn map_name(&self, ctx: &mut CheatCtx) -> Result<String, Error> {
let ptr = ctx.process.read_addr64(self.0 + 0x188)?;
let ptr = ctx.memory.read_addr64(self.0 + 0x188)?;
Ok(ctx.process.read_char_string_n(ptr, 32)?)
}
}

View File

@@ -8,4 +8,6 @@ pub trait BaseEntity {
fn from_index(ctx: &mut CheatCtx, entity_list: Address, index: i32) -> Result<Option<Self>, Error> where Self: std::marker::Sized;
fn pos(&self, ctx: &mut CheatCtx) -> Result<Vec3, Error>;
fn class_name(&self, ctx: &mut CheatCtx) -> Result<String, Error>;
fn next_by_class(&self, ctx: &mut CheatCtx) -> Result<Self, Error> where Self: std::marker::Sized;
fn previous_by_class(&self, ctx: &mut CheatCtx) -> Result<Self, Error> where Self: std::marker::Sized;
}