diff --git a/.gitignore b/.gitignore index 177cc3a..918d083 100755 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /target /src/dma/cs2dumper/offsets_mod.rs /src/dma/cs2dumper/client_mod.rs -/src/dma/cs2dumper/engine2_mod.rs \ No newline at end of file +/src/dma/cs2dumper/engine2_mod.rs +dump.sh diff --git a/src/cli.rs b/src/cli.rs index 3d83ab6..9106c89 100755 --- a/src/cli.rs +++ b/src/cli.rs @@ -6,7 +6,7 @@ use memflow::plugins::Inventory; use crate::dma::Connector; const PORT_RANGE: std::ops::RangeInclusive<usize> = 8000..=65535; -#[derive(Parser)] +#[derive(Parser, Clone)] #[command(author, version = version(), about, long_about = None)] pub struct Cli { /// Specifies the connector type for DMA diff --git a/src/comms.rs b/src/comms.rs index 8a9a90c..90ea0b0 100755 --- a/src/comms.rs +++ b/src/comms.rs @@ -51,6 +51,20 @@ pub enum EntityData { Bomb(BombData) } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CheatOptions { + #[serde(rename = "revealMoney")] + pub reveal_money: bool, +} + +impl Default for CheatOptions { + fn default() -> Self { + Self { + reveal_money: false, + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RadarData { freq: usize, @@ -83,13 +97,30 @@ pub struct RadarData { #[serde(rename(serialize = "entityData"))] player_data: Vec<EntityData>, - //#[serde(rename(serialize = "localYaw"))] - //local_yaw: f32, + #[serde(rename = "options")] + options: CheatOptions, + + #[serde(skip)] + pub money_reveal_enabled: bool, } impl RadarData { pub fn new(ingame: bool, map_name: String, player_data: Vec<EntityData>, freq: usize, bomb_planted: bool, bomb_cannot_defuse: bool, bomb_defuse_timeleft: f32, bomb_exploded: bool, bomb_being_defused: bool, bomb_defuse_length: f32, bomb_defuse_end: f32) -> RadarData { - RadarData { ingame, map_name, player_data, freq, bomb_planted, bomb_can_defuse: bomb_cannot_defuse, bomb_defuse_timeleft, bomb_exploded, bomb_being_defused, bomb_defuse_length, bomb_defuse_end } + RadarData { + ingame, + map_name, + player_data, + freq, + bomb_planted, + bomb_can_defuse: bomb_cannot_defuse, + bomb_defuse_timeleft, + bomb_exploded, + bomb_being_defused, + bomb_defuse_length, + bomb_defuse_end, + options: CheatOptions::default(), + money_reveal_enabled: false + } } /// Returns empty RadarData, it's also the same data that gets sent to clients when not ingame @@ -105,9 +136,20 @@ impl RadarData { bomb_exploded: false, bomb_being_defused: false, bomb_defuse_length: 0.0, - bomb_defuse_end: 0.0 + bomb_defuse_end: 0.0, + options: CheatOptions::default(), + money_reveal_enabled: false } } + + pub fn set_reveal_money(&mut self, value: bool) { + self.options.reveal_money = value; + self.money_reveal_enabled = value; + } + + pub fn get_reveal_money(&self) -> bool { + self.options.reveal_money + } } unsafe impl Send for RadarData {} diff --git a/src/dma/mod.rs b/src/dma/mod.rs index c23e670..7e4bc42 100755 --- a/src/dma/mod.rs +++ b/src/dma/mod.rs @@ -4,17 +4,28 @@ use memflow::{mem::MemoryView, os::Process, types::Address}; use crate::{enums::PlayerType, comms::{EntityData, PlayerData, RadarData, ArcRwlockRadarData, BombData}}; +use crate::money_reveal::MoneyReveal; + use self::{context::DmaCtx, threaddata::CsData}; -mod context; -mod threaddata; +pub mod context; +pub mod threaddata; mod cs2dumper; pub use context::Connector; pub async fn run(radar_data: ArcRwlockRadarData, connector: Connector, pcileech_device: String, skip_version: bool) -> anyhow::Result<()> { let mut ctx = DmaCtx::setup(connector, pcileech_device, skip_version)?; - let mut data = CsData { recheck_bomb_holder: true, ..Default::default() }; + let mut data = CsData { + recheck_bomb_holder: true, + money_reveal_enabled: false, + ..Default::default() + }; + + let mut money_reveal = MoneyReveal::new(); + if let Err(e) = money_reveal.init(&mut ctx.process, &ctx.client_module) { + log::warn!("Failed to initialize money reveal: {}", e); + } // For read timing let mut last_bomb_dropped = false; @@ -47,6 +58,17 @@ pub async fn run(radar_data: ArcRwlockRadarData, connector: Connector, pcileech_ data.update_common(&mut ctx); + { + let radar = radar_data.read().await; + if radar.money_reveal_enabled != data.money_reveal_enabled { + data.money_reveal_enabled = radar.money_reveal_enabled; + + if let Err(e) = money_reveal.toggle(&mut ctx.process) { + log::warn!("Failed to toggle money reveal: {}", e); + } + } + } + // Bomb update if (data.bomb_dropped && !last_bomb_dropped) || (data.bomb_planted && !last_bomb_planted) { data.update_bomb(&mut ctx); @@ -247,9 +269,12 @@ pub async fn run(radar_data: ArcRwlockRadarData, connector: Connector, pcileech_ data.bomb_defuse_length, bomb_defuse_end ); + + radar.money_reveal_enabled = data.money_reveal_enabled; } else { let mut radar = radar_data.write().await; *radar = RadarData::empty(freq); + radar.money_reveal_enabled = data.money_reveal_enabled; } last_tick_count = data.tick_count; @@ -264,5 +289,10 @@ pub async fn run(radar_data: ArcRwlockRadarData, connector: Connector, pcileech_ thread::sleep(Duration::from_millis(1)); } + let cleanup_result = money_reveal.ensure_disabled(&mut ctx.process); + if let Err(e) = cleanup_result { + log::warn!("Failed to cleanup money reveal: {}", e); + } + Ok(()) } \ No newline at end of file diff --git a/src/dma/threaddata/mod.rs b/src/dma/threaddata/mod.rs index f214d18..76640fb 100755 --- a/src/dma/threaddata/mod.rs +++ b/src/dma/threaddata/mod.rs @@ -38,6 +38,7 @@ pub struct CsData { pub bomb_defuse_length: f32, pub bomb_exploded: bool, pub bomb_defused: bool, + pub money_reveal_enabled: bool, } diff --git a/src/main.rs b/src/main.rs index 2d9619b..397e886 100755 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,9 @@ mod comms; mod dma; mod websocket; +mod pattern; +mod money_reveal; + #[tokio::main] async fn main() -> anyhow::Result<()> { let cli: Cli = Cli::parse(); @@ -29,6 +32,7 @@ async fn main() -> anyhow::Result<()> { ); let radar_clone = radar_data.clone(); + let dma_handle = tokio::spawn(async move { if let Err(err) = dma::run(radar_clone, cli.connector, cli.pcileech_device, cli.skip_version).await { log::error!("Error in dma thread: [{}]", err.to_string()); @@ -37,20 +41,23 @@ async fn main() -> anyhow::Result<()> { } }); + let web_path = cli.web_path.clone(); + let port = cli.port; + let _websocket_handle = tokio::spawn(async move { if let Ok(my_local_ip) = local_ip_address::local_ip() { - let address = format!("http://{}:{}", my_local_ip, cli.port); + let address = format!("http://{}:{}", my_local_ip, port); println!("Launched webserver at {}", address); } else { - let address = format!("http://0.0.0.0:{}", cli.port); + let address = format!("http://0.0.0.0:{}", port); println!("launched webserver at {}", address); } - if let Err(err) = websocket::run(cli.web_path, cli.port, radar_data).await { + if let Err(err) = websocket::run(web_path, port, radar_data).await { log::error!("Error in ws server: [{}]", err.to_string()); } }); dma_handle.await?; Ok(()) -} +} \ No newline at end of file diff --git a/src/money_reveal.rs b/src/money_reveal.rs new file mode 100644 index 0000000..db85dec --- /dev/null +++ b/src/money_reveal.rs @@ -0,0 +1,96 @@ +use memflow::{mem::MemoryView, types::Address, os::ModuleInfo}; +use crate::pattern::pattern_scan; + +const BUF_SIZE: usize = 3; + +pub struct MoneyReveal { + pub is_enabled: bool, + pub address: Option<Address>, + original_bytes: Option<[u8; BUF_SIZE]>, +} + +impl MoneyReveal { + pub fn new() -> Self { + Self { + is_enabled: false, + address: None, + original_bytes: None, + } + } + + pub fn init(&mut self, mem: &mut impl MemoryView, client_module: &ModuleInfo) -> anyhow::Result<()> { + self.address = self.find_function(mem, client_module)?; + log::info!("Money reveal function found at: {:?}", self.address); + Ok(()) + } + + pub fn toggle(&mut self, mem: &mut impl MemoryView) -> anyhow::Result<bool> { + if let Some(addr) = self.address { + if self.is_enabled { + if let Some(original) = self.original_bytes { + self.restore(mem, addr, original)?; + self.original_bytes = None; + self.is_enabled = false; + } + } else { + let original = self.patch(mem, addr)?; + self.original_bytes = Some(original); + self.is_enabled = true; + } + Ok(self.is_enabled) + } else { + Err(anyhow::anyhow!("Money reveal not initialized")) + } + } + + pub fn ensure_disabled(&mut self, mem: &mut impl MemoryView) -> anyhow::Result<()> { + if self.is_enabled { + if let Some(addr) = self.address { + if let Some(original) = self.original_bytes { + self.restore(mem, addr, original)?; + self.is_enabled = false; + } + } + } + Ok(()) + } + + fn find_function(&self, mem: &mut impl MemoryView, module: &ModuleInfo) -> anyhow::Result<Option<Address>> { + let is_hltv = pattern_scan( + mem, + module, + "48 83 EC 28 48 8B 0D ?? ?? ?? ?? 48 8B 01 FF 90 ?? ?? ?? ?? 84 C0 75 0D" + )?; + + if is_hltv.is_none() { + Ok(pattern_scan( + mem, + module, + "B0 01 C3 28 48 8B 0D ?? ?? ?? ?? 48 8B 01 FF 90 ?? ?? ?? ?? 84 C0 75 0D" + )?) + } else { + Ok(is_hltv) + } + } + + fn patch(&self, mem: &mut impl MemoryView, location: Address) -> anyhow::Result<[u8; BUF_SIZE]> { + let mut original_buf = [0u8; BUF_SIZE]; + mem.read_into(location, &mut original_buf)?; + + let new_buf: [u8; BUF_SIZE] = [ + 0xB0, 0x01, // MOV AL,1 + 0xC3 // RET + ]; + + log::debug!("Patching memory for money reveal"); + mem.write(location, &new_buf)?; + + Ok(original_buf) + } + + fn restore(&self, mem: &mut impl MemoryView, location: Address, original: [u8; BUF_SIZE]) -> anyhow::Result<()> { + log::debug!("Restoring memory for money reveal"); + mem.write(location, &original)?; + Ok(()) + } +} \ No newline at end of file diff --git a/src/pattern.rs b/src/pattern.rs new file mode 100644 index 0000000..c85d7f0 --- /dev/null +++ b/src/pattern.rs @@ -0,0 +1,51 @@ +use std::io::Read; +use log::warn; +use memflow::{os::ModuleInfo, mem::MemoryView, types::Address}; + +fn pattern_to_bytes(pattern: String) -> Vec<Option<u8>> { + pattern.split(' ') + .fold(Vec::new(), |mut accum, str| { + if str == "??" { + accum.push(None); + } else { + let byte = u8::from_str_radix(str, 16).unwrap(); + accum.push(Some(byte)); + } + + accum + }) +} + +pub fn pattern_scan(mem: &mut impl MemoryView, module: &ModuleInfo, sig: &str) -> anyhow::Result<Option<Address>> { + let pattern_bytes = pattern_to_bytes(sig.to_owned()); + let mut cursor = mem.cursor(); + + log::debug!("Searching \"{}\" for pattern: \"{}\"", module.name, sig); + + for i in 0..module.size { + let mut found = true; + cursor.set_address(module.base + i); + + let mut buf = vec![0u8; pattern_bytes.len()]; + + if let Err(_) = cursor.read(&mut buf) { + warn!("Encountered read error while scanning for pattern"); + continue; + } + + for (idx, byte) in buf.iter().enumerate() { + if let Some(pat_byte) = pattern_bytes[idx] { + if *byte != pat_byte { + found = false; + break; + } + } + } + + if found { + return Ok(Some(module.base + i)); + } + } + + Ok(None) +} \ No newline at end of file diff --git a/src/websocket.rs b/src/websocket.rs index 46a9e97..b4fd054 100644 --- a/src/websocket.rs +++ b/src/websocket.rs @@ -25,29 +25,43 @@ async fn ws_handler(ws: WebSocketUpgrade, State(state): State<AppState>) -> Resp 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 str = { - let data = state.data_lock.read().await; + if let Ok(text) = msg.to_text() { + if text == "requestInfo" { + let str = { + let data = state.data_lock.read().await; - 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() - }, + 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() { + return; } - }; + } else if text == "toggleMoneyReveal" { + let new_value = { + let mut data = state.data_lock.write().await; + data.money_reveal_enabled = !data.money_reveal_enabled; + data.money_reveal_enabled + }; - //println!("{str}"); + let response = serde_json::json!({ + "action": "toggleMoneyReveal", + "status": "success", + "enabled": new_value + }); - if socket.send(Message::Text(str)).await.is_err() { - // client disconnected - return; + if socket.send(Message::Text(response.to_string())).await.is_err() { + return; + } } } } else { - // client disconnected return; } } diff --git a/webradar/index.html b/webradar/index.html index 5729dd9..e847d08 100644 --- a/webradar/index.html +++ b/webradar/index.html @@ -26,6 +26,10 @@ <input type="checkbox" onclick="toggleGuns()" id="gunsCheck" name="guns"/> <label for="gunsCheck">Weapons</label> </div> + <div> + <input type="checkbox" onclick="toggleMoneyReveal()" id="moneyReveal" name="money"/> + <label for="moneyReveal">Money Reveal (DANGEROUS!)</label> + </div> </div> </div> <canvas id="canvas"></canvas> diff --git a/webradar/script.js b/webradar/script.js index 5eba57c..a1b7a6c 100644 --- a/webradar/script.js +++ b/webradar/script.js @@ -652,28 +652,36 @@ function connect() { if (event.data == "error") { console.log("[radarflow] Server had an unknown error") } else { - let data = JSON.parse(event.data); - radarData = data; - freq = data.freq; + try { + let data = JSON.parse(event.data); + radarData = data; + freq = data.freq; - if (data.ingame == false) { - mapName = null - entityData = null - - if (loaded) - unloadMap() - } else { - if (!loaded) { - mapName = data.mapName - entityData = data.entityData - loadMap(mapName) - } else { - entityData = data.entityData + if (data.money_reveal_enabled !== undefined) { + document.getElementById("moneyReveal").checked = data.money_reveal_enabled; } - } - update = true - requestAnimationFrame(render); + if (data.ingame == false) { + mapName = null + entityData = null + + if (loaded) + unloadMap() + } else { + if (!loaded) { + mapName = data.mapName + entityData = data.entityData + loadMap(mapName) + } else { + entityData = data.entityData + } + } + + update = true + requestAnimationFrame(render); + } catch (e) { + console.error("[radarflow] Error parsing server message:", e, event.data); + } } }; @@ -710,6 +718,7 @@ addEventListener("DOMContentLoaded", (e) => { document.getElementById("statsCheck").checked = true; document.getElementById("namesCheck").checked = true; document.getElementById("gunsCheck").checked = true; + document.getElementById("moneyReveal").checked = false; canvas = document.getElementById('canvas'); canvas.width = 1024; @@ -735,4 +744,10 @@ function toggleNames() { function toggleGuns() { drawGuns = !drawGuns +} + +function toggleMoneyReveal() { + if (websocket && websocket.readyState === WebSocket.OPEN) { + websocket.send("toggleMoneyReveal"); + } } \ No newline at end of file