Money reveal + writing memory
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -2,3 +2,4 @@ | |||||||
| /src/dma/cs2dumper/offsets_mod.rs | /src/dma/cs2dumper/offsets_mod.rs | ||||||
| /src/dma/cs2dumper/client_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; | use crate::dma::Connector; | ||||||
| const PORT_RANGE: std::ops::RangeInclusive<usize> = 8000..=65535; | const PORT_RANGE: std::ops::RangeInclusive<usize> = 8000..=65535; | ||||||
|  |  | ||||||
| #[derive(Parser)] | #[derive(Parser, Clone)] | ||||||
| #[command(author, version = version(), about, long_about = None)] | #[command(author, version = version(), about, long_about = None)] | ||||||
| pub struct Cli { | pub struct Cli { | ||||||
|     /// Specifies the connector type for DMA |     /// Specifies the connector type for DMA | ||||||
|   | |||||||
							
								
								
									
										50
									
								
								src/comms.rs
									
									
									
									
									
								
							
							
						
						
									
										50
									
								
								src/comms.rs
									
									
									
									
									
								
							| @@ -51,6 +51,20 @@ pub enum EntityData { | |||||||
|     Bomb(BombData) |     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)] | #[derive(Debug, Clone, Serialize, Deserialize)] | ||||||
| pub struct RadarData { | pub struct RadarData { | ||||||
|     freq: usize, |     freq: usize, | ||||||
| @@ -83,13 +97,30 @@ pub struct RadarData { | |||||||
|     #[serde(rename(serialize = "entityData"))] |     #[serde(rename(serialize = "entityData"))] | ||||||
|     player_data: Vec<EntityData>, |     player_data: Vec<EntityData>, | ||||||
|  |  | ||||||
|     //#[serde(rename(serialize = "localYaw"))] |     #[serde(rename = "options")] | ||||||
|     //local_yaw: f32, |     options: CheatOptions, | ||||||
|  |  | ||||||
|  |     #[serde(skip)] | ||||||
|  |     pub money_reveal_enabled: bool, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl RadarData { | 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 { |     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 |     /// 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_exploded: false, | ||||||
|             bomb_being_defused: false, |             bomb_being_defused: false, | ||||||
|             bomb_defuse_length: 0.0, |             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 {} | 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::{enums::PlayerType, comms::{EntityData, PlayerData, RadarData, ArcRwlockRadarData, BombData}}; | ||||||
|  |  | ||||||
|  | use crate::money_reveal::MoneyReveal; | ||||||
|  |  | ||||||
| use self::{context::DmaCtx, threaddata::CsData}; | use self::{context::DmaCtx, threaddata::CsData}; | ||||||
|  |  | ||||||
| mod context; | pub mod context; | ||||||
| mod threaddata; | pub mod threaddata; | ||||||
| mod cs2dumper; | mod cs2dumper; | ||||||
|  |  | ||||||
| pub use context::Connector; | pub use context::Connector; | ||||||
|  |  | ||||||
| pub async fn run(radar_data: ArcRwlockRadarData, connector: Connector, pcileech_device: String, skip_version: bool) -> anyhow::Result<()> { | 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 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 |     // For read timing | ||||||
|     let mut last_bomb_dropped = false; |     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); |         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 |         // Bomb update | ||||||
|         if (data.bomb_dropped && !last_bomb_dropped) || (data.bomb_planted && !last_bomb_planted) { |         if (data.bomb_dropped && !last_bomb_dropped) || (data.bomb_planted && !last_bomb_planted) { | ||||||
|             data.update_bomb(&mut ctx); |             data.update_bomb(&mut ctx); | ||||||
| @@ -247,9 +269,12 @@ pub async fn run(radar_data: ArcRwlockRadarData, connector: Connector, pcileech_ | |||||||
|                 data.bomb_defuse_length, |                 data.bomb_defuse_length, | ||||||
|                 bomb_defuse_end |                 bomb_defuse_end | ||||||
|             ); |             ); | ||||||
|  |  | ||||||
|  |             radar.money_reveal_enabled = data.money_reveal_enabled; | ||||||
|         } else { |         } else { | ||||||
|             let mut radar = radar_data.write().await; |             let mut radar = radar_data.write().await; | ||||||
|             *radar = RadarData::empty(freq); |             *radar = RadarData::empty(freq); | ||||||
|  |             radar.money_reveal_enabled = data.money_reveal_enabled; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         last_tick_count = data.tick_count; |         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)); |         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(()) |     Ok(()) | ||||||
| } | } | ||||||
| @@ -38,6 +38,7 @@ pub struct CsData { | |||||||
|     pub bomb_defuse_length: f32, |     pub bomb_defuse_length: f32, | ||||||
|     pub bomb_exploded: bool, |     pub bomb_exploded: bool, | ||||||
|     pub bomb_defused: bool, |     pub bomb_defused: bool, | ||||||
|  |     pub money_reveal_enabled: bool, | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										13
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								src/main.rs
									
									
									
									
									
								
							| @@ -13,6 +13,9 @@ mod comms; | |||||||
| mod dma; | mod dma; | ||||||
| mod websocket; | mod websocket; | ||||||
|  |  | ||||||
|  | mod pattern; | ||||||
|  | mod money_reveal; | ||||||
|  |  | ||||||
| #[tokio::main] | #[tokio::main] | ||||||
| async fn main() -> anyhow::Result<()> { | async fn main() -> anyhow::Result<()> { | ||||||
|     let cli: Cli = Cli::parse(); |     let cli: Cli = Cli::parse(); | ||||||
| @@ -29,6 +32,7 @@ async fn main() -> anyhow::Result<()> { | |||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     let radar_clone = radar_data.clone(); |     let radar_clone = radar_data.clone(); | ||||||
|  |  | ||||||
|     let dma_handle = tokio::spawn(async move { |     let dma_handle = tokio::spawn(async move { | ||||||
|         if let Err(err) = dma::run(radar_clone, cli.connector, cli.pcileech_device, cli.skip_version).await { |         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()); |             log::error!("Error in dma thread: [{}]", err.to_string()); | ||||||
| @@ -37,16 +41,19 @@ async fn main() -> anyhow::Result<()> { | |||||||
|         } |         } | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  |     let web_path = cli.web_path.clone(); | ||||||
|  |     let port = cli.port; | ||||||
|  |  | ||||||
|     let _websocket_handle = tokio::spawn(async move { |     let _websocket_handle = tokio::spawn(async move { | ||||||
|         if let Ok(my_local_ip) = local_ip_address::local_ip() { |         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); |             println!("Launched webserver at {}", address); | ||||||
|         } else { |         } 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); |             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()); |             log::error!("Error in ws server: [{}]", err.to_string()); | ||||||
|         } |         } | ||||||
|     }); |     }); | ||||||
|   | |||||||
							
								
								
									
										96
									
								
								src/money_reveal.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										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
									
								
							
							
						
						
									
										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,7 +25,8 @@ async fn ws_handler(ws: WebSocketUpgrade, State(state): State<AppState>) -> Resp | |||||||
| async fn handle_socket(mut socket: WebSocket, state: AppState) { | async fn handle_socket(mut socket: WebSocket, state: AppState) { | ||||||
|     while let Some(msg) = socket.recv().await { |     while let Some(msg) = socket.recv().await { | ||||||
|         if let Ok(msg) = msg { |         if let Ok(msg) = msg { | ||||||
|             if msg == Message::Text("requestInfo".to_string()) { |             if let Ok(text) = msg.to_text() { | ||||||
|  |                 if text == "requestInfo" { | ||||||
|                     let str = { |                     let str = { | ||||||
|                         let data = state.data_lock.read().await; |                         let data = state.data_lock.read().await; | ||||||
|  |  | ||||||
| @@ -39,15 +40,28 @@ async fn handle_socket(mut socket: WebSocket, state: AppState) { | |||||||
|                         } |                         } | ||||||
|                     }; |                     }; | ||||||
|  |  | ||||||
|                 //println!("{str}"); |  | ||||||
|  |  | ||||||
|                     if socket.send(Message::Text(str)).await.is_err() { |                     if socket.send(Message::Text(str)).await.is_err() { | ||||||
|                     // client disconnected |                         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 | ||||||
|  |                     }; | ||||||
|  |  | ||||||
|  |                     let response = serde_json::json!({ | ||||||
|  |                         "action": "toggleMoneyReveal", | ||||||
|  |                         "status": "success", | ||||||
|  |                         "enabled": new_value | ||||||
|  |                     }); | ||||||
|  |  | ||||||
|  |                     if socket.send(Message::Text(response.to_string())).await.is_err() { | ||||||
|                         return; |                         return; | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |             } | ||||||
|         } else { |         } else { | ||||||
|             // client disconnected |  | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -26,6 +26,10 @@ | |||||||
|                     <input type="checkbox" onclick="toggleGuns()" id="gunsCheck" name="guns"/> |                     <input type="checkbox" onclick="toggleGuns()" id="gunsCheck" name="guns"/> | ||||||
|                     <label for="gunsCheck">Weapons</label> |                     <label for="gunsCheck">Weapons</label> | ||||||
|                 </div> |                 </div> | ||||||
|  |                 <div> | ||||||
|  |                     <input type="checkbox" onclick="toggleMoneyReveal()" id="moneyReveal" name="money"/> | ||||||
|  |                     <label for="moneyReveal">Money Reveal (DANGEROUS!)</label> | ||||||
|  |                 </div> | ||||||
|             </div> |             </div> | ||||||
|         </div> |         </div> | ||||||
|         <canvas id="canvas"></canvas> |         <canvas id="canvas"></canvas> | ||||||
|   | |||||||
| @@ -652,10 +652,15 @@ function connect() { | |||||||
|             if (event.data == "error") { |             if (event.data == "error") { | ||||||
|                 console.log("[radarflow] Server had an unknown error") |                 console.log("[radarflow] Server had an unknown error") | ||||||
|             } else { |             } else { | ||||||
|  |                 try { | ||||||
|                     let data = JSON.parse(event.data); |                     let data = JSON.parse(event.data); | ||||||
|                     radarData = data; |                     radarData = data; | ||||||
|                     freq = data.freq; |                     freq = data.freq; | ||||||
|  |  | ||||||
|  |                     if (data.money_reveal_enabled !== undefined) { | ||||||
|  |                         document.getElementById("moneyReveal").checked = data.money_reveal_enabled; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|                     if (data.ingame == false) { |                     if (data.ingame == false) { | ||||||
|                         mapName = null |                         mapName = null | ||||||
|                         entityData = null |                         entityData = null | ||||||
| @@ -674,6 +679,9 @@ function connect() { | |||||||
|  |  | ||||||
|                     update = true |                     update = true | ||||||
|                     requestAnimationFrame(render); |                     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("statsCheck").checked = true; | ||||||
|     document.getElementById("namesCheck").checked = true; |     document.getElementById("namesCheck").checked = true; | ||||||
|     document.getElementById("gunsCheck").checked = true; |     document.getElementById("gunsCheck").checked = true; | ||||||
|  |     document.getElementById("moneyReveal").checked = false; | ||||||
|  |  | ||||||
|     canvas = document.getElementById('canvas'); |     canvas = document.getElementById('canvas'); | ||||||
|     canvas.width = 1024; |     canvas.width = 1024; | ||||||
| @@ -736,3 +745,9 @@ function toggleNames() { | |||||||
| function toggleGuns() { | function toggleGuns() { | ||||||
|     drawGuns = !drawGuns |     drawGuns = !drawGuns | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function toggleMoneyReveal() { | ||||||
|  |     if (websocket && websocket.readyState === WebSocket.OPEN) { | ||||||
|  |         websocket.send("toggleMoneyReveal"); | ||||||
|  |     } | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user