Initial Commit
This commit is contained in:
93
src/cli.rs
Normal file
93
src/cli.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::Parser;
|
||||
use memflow::prelude::Inventory;
|
||||
|
||||
use crate::structs::{Connector, Loglevel};
|
||||
|
||||
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)]
|
||||
pub struct Cli {
|
||||
/// Connector to use for DMA
|
||||
#[clap(value_enum, short, long, ignore_case = true, default_value_t = Connector::Qemu)]
|
||||
pub connector: Connector,
|
||||
|
||||
/// Pcileech device name
|
||||
#[clap(long, default_value_t = String::from("FPGA"))]
|
||||
pub pcileech_device: String,
|
||||
|
||||
/// Port to run Webserver on
|
||||
#[arg(short, long, default_value_t = 8000, value_parser = port_in_range)]
|
||||
pub port: u16,
|
||||
|
||||
/// Path to serve on 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
|
||||
#[arg(short = 'r', long, default_value_t = 60, value_parser = poll_in_range)]
|
||||
pub poll_rate: u16,
|
||||
|
||||
/// Loglevel verbosity
|
||||
#[arg(value_enum, long, short, ignore_case = true, default_value_t = Loglevel::Warn)]
|
||||
pub loglevel: Loglevel,
|
||||
}
|
||||
|
||||
fn version() -> String {
|
||||
let pkg_ver = env!("CARGO_PKG_VERSION");
|
||||
let git_hash = option_env!("VERGEN_GIT_SHA").unwrap_or("unknown");
|
||||
let commit_date = option_env!("VERGEN_GIT_COMMIT_DATE").unwrap_or("unknown");
|
||||
let avail_cons = {
|
||||
let inventory = Inventory::scan();
|
||||
inventory.available_connectors().join(", ")
|
||||
};
|
||||
|
||||
format!(" {pkg_ver} (rev {git_hash})\nCommit Date: {commit_date}\nAvailable Connectors: {avail_cons}")
|
||||
}
|
||||
|
||||
fn port_in_range(s: &str) -> Result<u16, String> {
|
||||
let port: usize = s
|
||||
.parse()
|
||||
.map_err(|_| format!("`{s}` isn't a port number"))?;
|
||||
if PORT_RANGE.contains(&port) {
|
||||
Ok(port as u16)
|
||||
} else {
|
||||
Err(format!(
|
||||
"port not in range {}-{}",
|
||||
PORT_RANGE.start(),
|
||||
PORT_RANGE.end()
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn valid_path(s: &str) -> Result<PathBuf, String> {
|
||||
let path = PathBuf::from(s);
|
||||
|
||||
if !path.exists() {
|
||||
return Err("Path does not exist".to_string())
|
||||
}
|
||||
|
||||
if !path.is_dir() {
|
||||
return Err("Path is not a directory".to_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()
|
||||
))
|
||||
}
|
||||
}
|
||||
268
src/dma/mod.rs
Normal file
268
src/dma/mod.rs
Normal file
@@ -0,0 +1,268 @@
|
||||
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}}};
|
||||
|
||||
pub struct CheatCtx {
|
||||
pub process: IntoProcessInstanceArcBox<'static>,
|
||||
pub client_module: ModuleInfo,
|
||||
pub engine_module: ModuleInfo,
|
||||
}
|
||||
|
||||
impl CheatCtx {
|
||||
pub fn setup(connector: Connector, pcileech_device: String) -> anyhow::Result<CheatCtx> {
|
||||
let inventory = Inventory::scan();
|
||||
|
||||
let os = {
|
||||
if connector == Connector::Pcileech {
|
||||
let args = Args::new()
|
||||
.insert("device", &pcileech_device);
|
||||
|
||||
let connector_args = ConnectorArgs::new(None, args, None);
|
||||
|
||||
inventory.builder()
|
||||
.connector(&connector.to_string())
|
||||
.args(connector_args)
|
||||
.os("win32")
|
||||
.build()?
|
||||
} else {
|
||||
inventory.builder()
|
||||
.connector(&connector.to_string())
|
||||
.os("win32")
|
||||
.build()?
|
||||
}
|
||||
};
|
||||
|
||||
let mut process = os.into_process_by_name("cs2.exe")?;
|
||||
|
||||
let client_module = process.module_by_name("client.dll")?;
|
||||
|
||||
let engine_module = process.module_by_name("engine2.dll")?;
|
||||
|
||||
let ctx = Self {
|
||||
process,
|
||||
client_module,
|
||||
engine_module,
|
||||
};
|
||||
|
||||
Ok(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
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<()> {
|
||||
let mut ctx = CheatCtx::setup(connector, pcileech_device)?;
|
||||
|
||||
// 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;
|
||||
|
||||
loop {
|
||||
if ctx.process.state().is_dead() {
|
||||
println!("is dead");
|
||||
break;
|
||||
}
|
||||
|
||||
if sdk::is_ingame(&mut ctx)? {
|
||||
let globals = sdk::get_globals(&mut ctx)?;
|
||||
let entity_list = sdk::get_entity_list(&mut ctx)?;
|
||||
let map_name = sdk::map_name(globals, &mut ctx)?;
|
||||
|
||||
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
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
let max_clients = sdk::max_clients(globals, &mut ctx)?;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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,
|
||||
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
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
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());
|
||||
|
||||
*/
|
||||
|
||||
} 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;
|
||||
}
|
||||
Duration::from_nanos(0)
|
||||
},
|
||||
};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
tokio_timerfd::sleep(remaining).await?;
|
||||
|
||||
#[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::trace!("elapsed: {}", elapsed.as_nanos());
|
||||
log::trace!("target: {}", target_interval.as_nanos());
|
||||
log::trace!("missmatch count: {}", missmatch_count);
|
||||
|
||||
last_iteration_time = Instant::now();
|
||||
}
|
||||
}
|
||||
|
||||
println!("DMA loop exited for some reason");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
62
src/main.rs
Normal file
62
src/main.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
use ::std::sync::Arc;
|
||||
use clap::Parser;
|
||||
|
||||
use cli::Cli;
|
||||
use structs::communication::RadarData;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
mod dma;
|
||||
mod sdk;
|
||||
mod structs;
|
||||
mod cli;
|
||||
mod webserver;
|
||||
|
||||
#[tokio::main]
|
||||
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()
|
||||
.expect("Initializing logger");
|
||||
|
||||
let rwlock = Arc::new(
|
||||
RwLock::new(
|
||||
RadarData::empty()
|
||||
)
|
||||
);
|
||||
|
||||
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 {
|
||||
log::error!("Error in dma thread: {}", err.to_string());
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
tokio::spawn(async move {
|
||||
let future = webserver::run(cli.web_path, cli.port, rwlock);
|
||||
|
||||
if let Ok(my_local_ip) = local_ip_address::local_ip() {
|
||||
let address = format!("http://{}:{}", my_local_ip, cli.port);
|
||||
println!("Launched webserver at {}", address);
|
||||
} else {
|
||||
let address = format!("http://0.0.0.0:{}", cli.port);
|
||||
println!("launched webserver at! {}", address);
|
||||
}
|
||||
|
||||
if let Err(err) = future.await {
|
||||
log::error!("Error in websocket server: {}", err.to_string());
|
||||
}
|
||||
});
|
||||
|
||||
if let Err(err) = dma_handle.await {
|
||||
log::error!("Error when waiting for dma thread: {}", err.to_string());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
4138
src/sdk/cs2dumper/client.rs
Normal file
4138
src/sdk/cs2dumper/client.rs
Normal file
File diff suppressed because it is too large
Load Diff
242
src/sdk/cs2dumper/engine2.rs
Normal file
242
src/sdk/cs2dumper/engine2.rs
Normal file
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* Created using https://github.com/a2x/cs2-dumper
|
||||
* Fri, 27 Oct 2023 01:03:22 +0000
|
||||
*/
|
||||
|
||||
#![allow(non_snake_case, non_upper_case_globals)]
|
||||
|
||||
pub mod CEmptyEntityInstance {
|
||||
}
|
||||
|
||||
pub mod CEntityComponent {
|
||||
}
|
||||
|
||||
pub mod CEntityComponentHelper {
|
||||
pub const m_flags: usize = 0x8; // uint32_t
|
||||
pub const m_pInfo: usize = 0x10; // EntComponentInfo_t*
|
||||
pub const m_nPriority: usize = 0x18; // int32_t
|
||||
pub const m_pNext: usize = 0x20; // CEntityComponentHelper*
|
||||
}
|
||||
|
||||
pub mod CEntityIOOutput {
|
||||
pub const m_Value: usize = 0x18; // CVariantBase<CVariantDefaultAllocator>
|
||||
}
|
||||
|
||||
pub mod CEntityIdentity {
|
||||
pub const m_nameStringableIndex: usize = 0x14; // int32_t
|
||||
pub const m_name: usize = 0x18; // CUtlSymbolLarge
|
||||
pub const m_designerName: usize = 0x20; // CUtlSymbolLarge
|
||||
pub const m_flags: usize = 0x30; // uint32_t
|
||||
pub const m_worldGroupId: usize = 0x38; // WorldGroupId_t
|
||||
pub const m_fDataObjectTypes: usize = 0x3C; // uint32_t
|
||||
pub const m_PathIndex: usize = 0x40; // ChangeAccessorFieldPathIndex_t
|
||||
pub const m_pPrev: usize = 0x58; // CEntityIdentity*
|
||||
pub const m_pNext: usize = 0x60; // CEntityIdentity*
|
||||
pub const m_pPrevByClass: usize = 0x68; // CEntityIdentity*
|
||||
pub const m_pNextByClass: usize = 0x70; // CEntityIdentity*
|
||||
}
|
||||
|
||||
pub mod CEntityInstance {
|
||||
pub const m_iszPrivateVScripts: usize = 0x8; // CUtlSymbolLarge
|
||||
pub const m_pEntity: usize = 0x10; // CEntityIdentity*
|
||||
pub const m_CScriptComponent: usize = 0x28; // CScriptComponent*
|
||||
}
|
||||
|
||||
pub mod CNetworkVarChainer {
|
||||
pub const m_PathIndex: usize = 0x20; // ChangeAccessorFieldPathIndex_t
|
||||
}
|
||||
|
||||
pub mod CScriptComponent { // CEntityComponent
|
||||
pub const m_scriptClassName: usize = 0x30; // CUtlSymbolLarge
|
||||
}
|
||||
|
||||
pub mod CVariantDefaultAllocator {
|
||||
}
|
||||
|
||||
pub mod EngineLoopState_t {
|
||||
pub const m_nPlatWindowWidth: usize = 0x18; // int32_t
|
||||
pub const m_nPlatWindowHeight: usize = 0x1C; // int32_t
|
||||
pub const m_nRenderWidth: usize = 0x20; // int32_t
|
||||
pub const m_nRenderHeight: usize = 0x24; // int32_t
|
||||
}
|
||||
|
||||
pub mod EntComponentInfo_t {
|
||||
pub const m_pName: usize = 0x0; // char*
|
||||
pub const m_pCPPClassname: usize = 0x8; // char*
|
||||
pub const m_pNetworkDataReferencedDescription: usize = 0x10; // char*
|
||||
pub const m_pNetworkDataReferencedPtrPropDescription: usize = 0x18; // char*
|
||||
pub const m_nRuntimeIndex: usize = 0x20; // int32_t
|
||||
pub const m_nFlags: usize = 0x24; // uint32_t
|
||||
pub const m_pBaseClassComponentHelper: usize = 0x60; // CEntityComponentHelper*
|
||||
}
|
||||
|
||||
pub mod EntInput_t {
|
||||
}
|
||||
|
||||
pub mod EntOutput_t {
|
||||
}
|
||||
|
||||
pub mod EventAdvanceTick_t { // EventSimulate_t
|
||||
pub const m_nCurrentTick: usize = 0x30; // int32_t
|
||||
pub const m_nCurrentTickThisFrame: usize = 0x34; // int32_t
|
||||
pub const m_nTotalTicksThisFrame: usize = 0x38; // int32_t
|
||||
pub const m_nTotalTicks: usize = 0x3C; // int32_t
|
||||
}
|
||||
|
||||
pub mod EventAppShutdown_t {
|
||||
pub const m_nDummy0: usize = 0x0; // int32_t
|
||||
}
|
||||
|
||||
pub mod EventClientAdvanceTick_t { // EventAdvanceTick_t
|
||||
}
|
||||
|
||||
pub mod EventClientFrameSimulate_t {
|
||||
pub const m_LoopState: usize = 0x0; // EngineLoopState_t
|
||||
pub const m_flRealTime: usize = 0x28; // float
|
||||
pub const m_flFrameTime: usize = 0x2C; // float
|
||||
}
|
||||
|
||||
pub mod EventClientOutput_t {
|
||||
pub const m_LoopState: usize = 0x0; // EngineLoopState_t
|
||||
pub const m_flRenderTime: usize = 0x28; // float
|
||||
pub const m_flRealTime: usize = 0x2C; // float
|
||||
pub const m_flRenderFrameTimeUnbounded: usize = 0x30; // float
|
||||
pub const m_bRenderOnly: usize = 0x34; // bool
|
||||
}
|
||||
|
||||
pub mod EventClientPauseSimulate_t { // EventSimulate_t
|
||||
}
|
||||
|
||||
pub mod EventClientPollInput_t {
|
||||
pub const m_LoopState: usize = 0x0; // EngineLoopState_t
|
||||
pub const m_flRealTime: usize = 0x28; // float
|
||||
}
|
||||
|
||||
pub mod EventClientPollNetworking_t {
|
||||
pub const m_nTickCount: usize = 0x0; // int32_t
|
||||
}
|
||||
|
||||
pub mod EventClientPostAdvanceTick_t { // EventPostAdvanceTick_t
|
||||
}
|
||||
|
||||
pub mod EventClientPostOutput_t {
|
||||
pub const m_LoopState: usize = 0x0; // EngineLoopState_t
|
||||
pub const m_flRenderTime: usize = 0x28; // double
|
||||
pub const m_flRenderFrameTime: usize = 0x30; // float
|
||||
pub const m_flRenderFrameTimeUnbounded: usize = 0x34; // float
|
||||
pub const m_bRenderOnly: usize = 0x38; // bool
|
||||
}
|
||||
|
||||
pub mod EventClientPostSimulate_t { // EventSimulate_t
|
||||
}
|
||||
|
||||
pub mod EventClientPreOutput_t {
|
||||
pub const m_LoopState: usize = 0x0; // EngineLoopState_t
|
||||
pub const m_flRenderTime: usize = 0x28; // double
|
||||
pub const m_flRenderFrameTime: usize = 0x30; // double
|
||||
pub const m_flRenderFrameTimeUnbounded: usize = 0x38; // double
|
||||
pub const m_flRealTime: usize = 0x40; // float
|
||||
pub const m_bRenderOnly: usize = 0x44; // bool
|
||||
}
|
||||
|
||||
pub mod EventClientPreSimulate_t { // EventSimulate_t
|
||||
}
|
||||
|
||||
pub mod EventClientPredictionPostNetupdate_t {
|
||||
}
|
||||
|
||||
pub mod EventClientProcessGameInput_t {
|
||||
pub const m_LoopState: usize = 0x0; // EngineLoopState_t
|
||||
pub const m_flRealTime: usize = 0x28; // float
|
||||
pub const m_flFrameTime: usize = 0x2C; // float
|
||||
}
|
||||
|
||||
pub mod EventClientProcessInput_t {
|
||||
pub const m_LoopState: usize = 0x0; // EngineLoopState_t
|
||||
pub const m_flRealTime: usize = 0x28; // float
|
||||
}
|
||||
|
||||
pub mod EventClientProcessNetworking_t {
|
||||
}
|
||||
|
||||
pub mod EventClientSceneSystemThreadStateChange_t {
|
||||
pub const m_bThreadsActive: usize = 0x0; // bool
|
||||
}
|
||||
|
||||
pub mod EventClientSendInput_t {
|
||||
pub const m_bFinalClientCommandTick: usize = 0x0; // bool
|
||||
pub const m_nAdditionalClientCommandsToCreate: usize = 0x4; // int32_t
|
||||
}
|
||||
|
||||
pub mod EventClientSimulate_t { // EventSimulate_t
|
||||
}
|
||||
|
||||
pub mod EventFrameBoundary_t {
|
||||
pub const m_flFrameTime: usize = 0x0; // float
|
||||
}
|
||||
|
||||
pub mod EventModInitialized_t {
|
||||
}
|
||||
|
||||
pub mod EventPostAdvanceTick_t { // EventSimulate_t
|
||||
pub const m_nCurrentTick: usize = 0x30; // int32_t
|
||||
pub const m_nCurrentTickThisFrame: usize = 0x34; // int32_t
|
||||
pub const m_nTotalTicksThisFrame: usize = 0x38; // int32_t
|
||||
pub const m_nTotalTicks: usize = 0x3C; // int32_t
|
||||
}
|
||||
|
||||
pub mod EventPostDataUpdate_t {
|
||||
pub const m_nCount: usize = 0x0; // int32_t
|
||||
}
|
||||
|
||||
pub mod EventPreDataUpdate_t {
|
||||
pub const m_nCount: usize = 0x0; // int32_t
|
||||
}
|
||||
|
||||
pub mod EventProfileStorageAvailable_t {
|
||||
pub const m_nSplitScreenSlot: usize = 0x0; // CSplitScreenSlot
|
||||
}
|
||||
|
||||
pub mod EventServerAdvanceTick_t { // EventAdvanceTick_t
|
||||
}
|
||||
|
||||
pub mod EventServerPollNetworking_t { // EventSimulate_t
|
||||
}
|
||||
|
||||
pub mod EventServerPostAdvanceTick_t { // EventPostAdvanceTick_t
|
||||
}
|
||||
|
||||
pub mod EventServerPostSimulate_t { // EventSimulate_t
|
||||
}
|
||||
|
||||
pub mod EventServerProcessNetworking_t { // EventSimulate_t
|
||||
}
|
||||
|
||||
pub mod EventServerSimulate_t { // EventSimulate_t
|
||||
}
|
||||
|
||||
pub mod EventSetTime_t {
|
||||
pub const m_LoopState: usize = 0x0; // EngineLoopState_t
|
||||
pub const m_nClientOutputFrames: usize = 0x28; // int32_t
|
||||
pub const m_flRealTime: usize = 0x30; // double
|
||||
pub const m_flRenderTime: usize = 0x38; // double
|
||||
pub const m_flRenderFrameTime: usize = 0x40; // double
|
||||
pub const m_flRenderFrameTimeUnbounded: usize = 0x48; // double
|
||||
pub const m_flRenderFrameTimeUnscaled: usize = 0x50; // double
|
||||
pub const m_flTickRemainder: usize = 0x58; // double
|
||||
}
|
||||
|
||||
pub mod EventSimpleLoopFrameUpdate_t {
|
||||
pub const m_LoopState: usize = 0x0; // EngineLoopState_t
|
||||
pub const m_flRealTime: usize = 0x28; // float
|
||||
pub const m_flFrameTime: usize = 0x2C; // float
|
||||
}
|
||||
|
||||
pub mod EventSimulate_t {
|
||||
pub const m_LoopState: usize = 0x0; // EngineLoopState_t
|
||||
pub const m_bFirstTick: usize = 0x28; // bool
|
||||
pub const m_bLastTick: usize = 0x29; // bool
|
||||
}
|
||||
|
||||
pub mod EventSplitScreenStateChanged_t {
|
||||
}
|
||||
4
src/sdk/cs2dumper/mod.rs
Normal file
4
src/sdk/cs2dumper/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
#![allow(dead_code)]
|
||||
pub mod client;
|
||||
pub mod engine2;
|
||||
pub mod offsets;
|
||||
46
src/sdk/cs2dumper/offsets.rs
Normal file
46
src/sdk/cs2dumper/offsets.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Created using https://github.com/a2x/cs2-dumper
|
||||
* Mon, 30 Oct 2023 00:17:09 +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 mod engine2_dll { // engine2.dll
|
||||
pub const dwBuildNumber: usize = 0x488514;
|
||||
pub const dwNetworkGameClient: usize = 0x487AB0;
|
||||
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 mod inputsystem_dll { // inputsystem.dll
|
||||
pub const dwInputSystem: usize = 0x35770;
|
||||
}
|
||||
43
src/sdk/mod.rs
Normal file
43
src/sdk/mod.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
pub mod structs;
|
||||
pub mod cs2dumper;
|
||||
use crate::dma::CheatCtx;
|
||||
|
||||
use memflow::prelude::v1::*;
|
||||
use anyhow::Result;
|
||||
|
||||
use self::structs::{CCSPlayerController, CPlayerPawn};
|
||||
|
||||
pub fn get_local(ctx: &mut CheatCtx) -> Result<CCSPlayerController> {
|
||||
let ptr = ctx.process.read_addr64(ctx.client_module.base + cs2dumper::offsets::client_dll::dwLocalPlayerController)?;
|
||||
Ok(CCSPlayerController::new(ptr))
|
||||
}
|
||||
|
||||
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(CPlayerPawn::new(ptr))
|
||||
}
|
||||
|
||||
pub fn get_entity_list(ctx: &mut CheatCtx) -> Result<Address> {
|
||||
let ptr = ctx.process.read_addr64(ctx.client_module.base + cs2dumper::offsets::client_dll::dwEntityList)?;
|
||||
Ok(ptr)
|
||||
}
|
||||
|
||||
pub fn get_globals(ctx: &mut CheatCtx) -> Result<Address> {
|
||||
let ptr = ctx.process.read_addr64(ctx.client_module.base + cs2dumper::offsets::client_dll::dwGlobalVars)?;
|
||||
Ok(ptr)
|
||||
}
|
||||
|
||||
pub fn map_name(global_vars: Address, ctx: &mut CheatCtx) -> Result<String> {
|
||||
let ptr = ctx.process.read_addr64(global_vars + 0x188)?;
|
||||
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 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)
|
||||
}
|
||||
206
src/sdk/structs/entity.rs
Normal file
206
src/sdk/structs/entity.rs
Normal file
@@ -0,0 +1,206 @@
|
||||
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 num_traits::FromPrimitive;
|
||||
|
||||
#[repr(i32)]
|
||||
#[derive(Debug, Eq, PartialEq, Primitive)]
|
||||
pub enum TeamID {
|
||||
Spectator = 1,
|
||||
T = 2,
|
||||
CT = 3
|
||||
}
|
||||
|
||||
pub struct CEntityIdentity(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))
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
DWORD64 entityPawnBase = Memory::Read<unsigned __int64>(EntitiesList + ((hEntity & 0x7FFF) * ENTITY_SPACING));
|
||||
auto pawn = read<C_CSPlayerPawnBase*>(entityPawnBase + 0x78 * (hEntity & 0x1FF));
|
||||
*/
|
||||
|
||||
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 ptr(&self) -> Address {
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub struct CCSPlayerController(Address);
|
||||
|
||||
impl CCSPlayerController {
|
||||
pub fn ptr(&self) -> Address {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn new(ptr: Address) -> CCSPlayerController {
|
||||
CCSPlayerController(ptr)
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
pub fn get_player_type(&self, ctx: &mut CheatCtx, local: &CCSPlayerController) -> 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, entity_list: Address, ctx: &mut CheatCtx) -> Result<Option<CPlayerPawn>> {
|
||||
let uhandle = ctx.process.read(self.0 + cs2dumper::client::CCSPlayerController::m_hPlayerPawn)?;
|
||||
Ok(CPlayerPawn::from_uhandle(uhandle, entity_list, ctx))
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
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)?)
|
||||
}
|
||||
|
||||
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 to_base(&self) -> CBaseEntity {
|
||||
CBaseEntity(self.0)
|
||||
}
|
||||
}
|
||||
2
src/sdk/structs/mod.rs
Normal file
2
src/sdk/structs/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
mod entity;
|
||||
pub use entity::*;
|
||||
79
src/structs/comms.rs
Normal file
79
src/structs/comms.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use super::Vec3;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PlayerData {
|
||||
pos: Vec3,
|
||||
yaw: f32,
|
||||
#[serde(rename = "playerType")]
|
||||
player_type: PlayerType,
|
||||
|
||||
#[serde(rename = "hasBomb")]
|
||||
has_bomb: bool
|
||||
}
|
||||
|
||||
impl PlayerData {
|
||||
pub fn new(pos: Vec3, yaw: f32, player_type: PlayerType, has_bomb: bool) -> PlayerData {
|
||||
PlayerData { pos, yaw, player_type, has_bomb }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct BombData {
|
||||
pos: Vec3,
|
||||
#[serde(rename = "isPlanted")]
|
||||
is_planted: bool
|
||||
}
|
||||
|
||||
impl BombData {
|
||||
pub fn new(pos: Vec3, is_planted: bool) -> BombData {
|
||||
BombData { pos, is_planted }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum EntityData {
|
||||
Player(PlayerData),
|
||||
Bomb(BombData)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
|
||||
pub enum PlayerType {
|
||||
#[default]
|
||||
Unknown,
|
||||
Spectator,
|
||||
Local,
|
||||
Enemy,
|
||||
Team
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RadarData {
|
||||
ingame: bool,
|
||||
|
||||
#[serde(rename = "mapName")]
|
||||
map_name: String,
|
||||
|
||||
#[serde(rename(serialize = "entityData"))]
|
||||
player_data: Vec<EntityData>,
|
||||
|
||||
#[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 }
|
||||
}
|
||||
|
||||
/// Returns empty RadarData, it's also the same data that gets sent to clients when not ingame
|
||||
pub fn empty() -> RadarData {
|
||||
RadarData {
|
||||
ingame: false,
|
||||
map_name: String::new(),
|
||||
player_data: Vec::new(),
|
||||
local_yaw: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
50
src/structs/mod.rs
Normal file
50
src/structs/mod.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
pub mod comms;
|
||||
|
||||
use clap::ValueEnum;
|
||||
pub use comms as communication;
|
||||
|
||||
mod vec3;
|
||||
pub use vec3::Vec3;
|
||||
|
||||
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Default)]
|
||||
pub enum Connector {
|
||||
#[default]
|
||||
Qemu,
|
||||
Kvm,
|
||||
Pcileech
|
||||
}
|
||||
|
||||
impl ToString for Connector {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
Connector::Qemu => String::from("qemu"),
|
||||
Connector::Kvm => String::from("kvm"),
|
||||
Connector::Pcileech => String::from("pcileech"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper because log::LevelFilter doesn't implement ValueEnum
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Default)]
|
||||
pub enum Loglevel {
|
||||
Error,
|
||||
#[default]
|
||||
Warn,
|
||||
Info,
|
||||
Debug,
|
||||
Trace,
|
||||
}
|
||||
|
||||
impl From<Loglevel> for log::LevelFilter {
|
||||
fn from(val: Loglevel) -> Self {
|
||||
match val {
|
||||
Loglevel::Error => log::LevelFilter::Error,
|
||||
Loglevel::Warn => log::LevelFilter::Warn,
|
||||
Loglevel::Info => log::LevelFilter::Info,
|
||||
Loglevel::Debug => log::LevelFilter::Debug,
|
||||
Loglevel::Trace => log::LevelFilter::Trace,
|
||||
}
|
||||
}
|
||||
}
|
||||
11
src/structs/vec3.rs
Normal file
11
src/structs/vec3.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, serde::Deserialize)]
|
||||
#[repr(C)]
|
||||
pub struct Vec3 {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub z: f32
|
||||
}
|
||||
|
||||
unsafe impl dataview::Pod for Vec3 {}
|
||||
66
src/webserver.rs
Normal file
66
src/webserver.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
use std::{sync::Arc, path::PathBuf};
|
||||
|
||||
use axum::{
|
||||
extract::{ws::{WebSocketUpgrade, WebSocket, Message}, State},
|
||||
response::Response,
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
|
||||
use tokio::sync::RwLock;
|
||||
use tower_http::services::ServeDir;
|
||||
|
||||
use crate::structs::communication::RadarData;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AppState {
|
||||
data_lock: Arc<RwLock<RadarData>>
|
||||
}
|
||||
|
||||
async fn ws_handler(ws: WebSocketUpgrade, State(state): State<AppState>) -> Response {
|
||||
ws.on_upgrade(|socket| handle_socket(socket, state))
|
||||
}
|
||||
|
||||
async fn handle_socket(mut socket: WebSocket, state: AppState) {
|
||||
while let Some(msg) = socket.recv().await {
|
||||
if let Ok(msg) = msg {
|
||||
if msg == Message::Text("requestInfo".to_string()) {
|
||||
let data = state.data_lock.read().await;
|
||||
|
||||
let str = {
|
||||
match serde_json::to_string(&*data) {
|
||||
Ok(json) => json,
|
||||
Err(e) => {
|
||||
log::error!("Could not serialize data into json: {}", e.to_string());
|
||||
log::error!("Sending \"error\" instead");
|
||||
"error".to_string()
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
if socket.send(Message::Text(str)).await.is_err() {
|
||||
// client disconnected
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// client disconnected
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(path: PathBuf, port: u16, data_lock: Arc<RwLock<RadarData>>) -> anyhow::Result<()> {
|
||||
let app = Router::new()
|
||||
.nest_service("/", ServeDir::new(path))
|
||||
.route("/ws", get(ws_handler))
|
||||
.with_state(AppState { data_lock });
|
||||
|
||||
let address = format!("0.0.0.0:{}", port);
|
||||
|
||||
axum::Server::bind(&address.parse()?)
|
||||
.serve(app.into_make_service())
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user