Money reveal + writing memory

This commit is contained in:
Wizzard 2025-03-14 17:29:12 -04:00
parent d6719b0d54
commit 5f99e03b4c
11 changed files with 309 additions and 48 deletions

3
.gitignore vendored

@ -1,4 +1,5 @@
/target
/src/dma/cs2dumper/offsets_mod.rs
/src/dma/cs2dumper/client_mod.rs
/src/dma/cs2dumper/engine2_mod.rs
/src/dma/cs2dumper/engine2_mod.rs
dump.sh

@ -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

@ -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 {}

@ -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(())
}

@ -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,
}

@ -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(())
}
}

96
src/money_reveal.rs Normal file

@ -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(())
}
}

51
src/pattern.rs Normal file

@ -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)
}

@ -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;
}
}

@ -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>

@ -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");
}
}