Compare commits
	
		
			17 Commits
		
	
	
		
			ba28f01247
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 1bdcbc8fb9 | |||
| d8982ead59 | |||
| 2df1b5a560 | |||
| a17146fee9 | |||
| 38ee14524f | |||
| b6300bcb0b | |||
| e9d197229a | |||
| 4846e045bb | |||
| d10fcdf15e | |||
| f5af3d6281 | |||
| 1eed104790 | |||
| 6f4e6a4f30 | |||
| 5e19f0af47 | |||
| ffa2979da3 | |||
| 3d0b960753 | |||
| 3a69f285f3 | |||
| cb9462ce9b | 
							
								
								
									
										15
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										15
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +1,6 @@ | |||||||
| # This file is automatically @generated by Cargo. | # This file is automatically @generated by Cargo. | ||||||
| # It is not intended for manual editing. | # It is not intended for manual editing. | ||||||
| version = 3 | version = 4 | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "abi_stable" | name = "abi_stable" | ||||||
| @@ -1886,12 +1886,15 @@ dependencies = [ | |||||||
|  "clap", |  "clap", | ||||||
|  "dataview 1.0.1", |  "dataview 1.0.1", | ||||||
|  "enum-primitive-derive", |  "enum-primitive-derive", | ||||||
|  |  "flate2", | ||||||
|  "itertools 0.13.0", |  "itertools 0.13.0", | ||||||
|  |  "lazy_static", | ||||||
|  "local-ip-address", |  "local-ip-address", | ||||||
|  "log", |  "log", | ||||||
|  "memflow", |  "memflow", | ||||||
|  "memflow-native", |  "memflow-native", | ||||||
|  "num-traits", |  "num-traits", | ||||||
|  |  "rand", | ||||||
|  "reqwest", |  "reqwest", | ||||||
|  "serde", |  "serde", | ||||||
|  "serde_json", |  "serde_json", | ||||||
| @@ -1899,6 +1902,7 @@ dependencies = [ | |||||||
|  "tokio", |  "tokio", | ||||||
|  "tower 0.5.1", |  "tower 0.5.1", | ||||||
|  "tower-http", |  "tower-http", | ||||||
|  |  "uuid", | ||||||
|  "vergen-gitcl", |  "vergen-gitcl", | ||||||
| ] | ] | ||||||
|  |  | ||||||
| @@ -2873,6 +2877,15 @@ version = "0.2.1" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "uuid" | ||||||
|  | version = "1.12.1" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" | ||||||
|  | dependencies = [ | ||||||
|  |  "getrandom", | ||||||
|  | ] | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "vcpkg" | name = "vcpkg" | ||||||
| version = "0.2.15" | version = "0.2.15" | ||||||
|   | |||||||
| @@ -38,6 +38,12 @@ local-ip-address = "0.6.3" | |||||||
| # other | # other | ||||||
| itertools = "0.13.0" | itertools = "0.13.0" | ||||||
|  |  | ||||||
|  | flate2 = "1.0" | ||||||
|  | rand = "0.8" | ||||||
|  |  | ||||||
|  | lazy_static = "1.4" | ||||||
|  |  | ||||||
|  | uuid = { version = "1.3", features = ["v4"] } | ||||||
|  |  | ||||||
| [build-dependencies] | [build-dependencies] | ||||||
| reqwest = { version = "0.12.9", features = ["blocking"] } | reqwest = { version = "0.12.9", features = ["blocking"] } | ||||||
|   | |||||||
							
								
								
									
										30
									
								
								src/comms.rs
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								src/comms.rs
									
									
									
									
									
								
							| @@ -23,11 +23,29 @@ pub struct PlayerData { | |||||||
|  |  | ||||||
|     #[serde(rename = "weaponId")] |     #[serde(rename = "weaponId")] | ||||||
|     weapon_id: i16, |     weapon_id: i16, | ||||||
|  |  | ||||||
|  |     #[serde(rename = "money", default)] | ||||||
|  |     money: i32, | ||||||
|  |  | ||||||
|  |     #[serde(rename = "health", default)] | ||||||
|  |     health: u32, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl PlayerData { | impl PlayerData { | ||||||
|     pub fn new(pos: Vec3, yaw: f32, player_type: PlayerType, has_bomb: bool, has_awp: bool, is_scoped: bool, player_name: String, weapon_id: i16) -> PlayerData { |     pub fn new(pos: Vec3, yaw: f32, player_type: PlayerType, has_bomb: bool, has_awp: bool, | ||||||
|         PlayerData { pos, yaw, player_type, has_bomb, has_awp, is_scoped, player_name, weapon_id } |                     is_scoped: bool, player_name: String, weapon_id: i16, money: i32, health: u32) -> PlayerData { | ||||||
|  |         PlayerData { | ||||||
|  |             pos, | ||||||
|  |             yaw, | ||||||
|  |             player_type, | ||||||
|  |             has_bomb, | ||||||
|  |             has_awp, | ||||||
|  |             is_scoped, | ||||||
|  |             player_name, | ||||||
|  |             weapon_id, | ||||||
|  |             money, | ||||||
|  |             health | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -55,12 +73,16 @@ pub enum EntityData { | |||||||
| pub struct CheatOptions { | pub struct CheatOptions { | ||||||
|     #[serde(rename = "revealMoney")] |     #[serde(rename = "revealMoney")] | ||||||
|     pub reveal_money: bool, |     pub reveal_money: bool, | ||||||
|  |  | ||||||
|  |     #[serde(rename = "displayMoney")] | ||||||
|  |     pub display_money: bool, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for CheatOptions { | impl Default for CheatOptions { | ||||||
|     fn default() -> Self { |     fn default() -> Self { | ||||||
|         Self { |         Self { | ||||||
|             reveal_money: false, |             reveal_money: false, | ||||||
|  |             display_money: true, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -141,6 +163,10 @@ impl RadarData { | |||||||
|             money_reveal_enabled: false |             money_reveal_enabled: false | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     pub fn get_entities(&self) -> &Vec<EntityData> { | ||||||
|  |         &self.player_data | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| unsafe impl Send for RadarData {} | unsafe impl Send for RadarData {} | ||||||
|   | |||||||
| @@ -98,6 +98,8 @@ impl DmaCtx { | |||||||
|         let mut clipping_weapon = 0u64; |         let mut clipping_weapon = 0u64; | ||||||
|         let mut is_scoped = 0u8; |         let mut is_scoped = 0u8; | ||||||
|         let mut player_name_ptr = 0u64; |         let mut player_name_ptr = 0u64; | ||||||
|  |         let mut money = 0i32; | ||||||
|  |         let mut money_services_ptr = 0u64; | ||||||
|  |  | ||||||
|         { |         { | ||||||
|             let mut batcher = MemoryViewBatcher::new(&mut self.process); |             let mut batcher = MemoryViewBatcher::new(&mut self.process); | ||||||
| @@ -108,6 +110,13 @@ impl DmaCtx { | |||||||
|             batcher.read_into(pawn + cs2dumper::client::C_CSPlayerPawnBase::m_pClippingWeapon, &mut clipping_weapon); |             batcher.read_into(pawn + cs2dumper::client::C_CSPlayerPawnBase::m_pClippingWeapon, &mut clipping_weapon); | ||||||
|             batcher.read_into(pawn + cs2dumper::client::C_CSPlayerPawn::m_bIsScoped, &mut is_scoped); |             batcher.read_into(pawn + cs2dumper::client::C_CSPlayerPawn::m_bIsScoped, &mut is_scoped); | ||||||
|             batcher.read_into(controller + cs2dumper::client::CCSPlayerController::m_sSanitizedPlayerName, &mut player_name_ptr); |             batcher.read_into(controller + cs2dumper::client::CCSPlayerController::m_sSanitizedPlayerName, &mut player_name_ptr); | ||||||
|  |  | ||||||
|  |             batcher.read_into(controller + cs2dumper::client::CCSPlayerController::m_pInGameMoneyServices, &mut money_services_ptr); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if money_services_ptr != 0 { | ||||||
|  |             let money_addr: Address = money_services_ptr.into(); | ||||||
|  |             money = self.process.read(money_addr + cs2dumper::client::CCSPlayerController_InGameMoneyServices::m_iAccount)?; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         let player_name = if player_name_ptr != 0 { |         let player_name = if player_name_ptr != 0 { | ||||||
| @@ -145,6 +154,7 @@ impl DmaCtx { | |||||||
|             is_scoped: is_scoped != 0, |             is_scoped: is_scoped != 0, | ||||||
|             player_name, |             player_name, | ||||||
|             weapon_id, |             weapon_id, | ||||||
|  |             money, | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -273,4 +283,5 @@ pub struct BatchedPlayerData { | |||||||
|     pub is_scoped: bool, |     pub is_scoped: bool, | ||||||
|     pub player_name: String, |     pub player_name: String, | ||||||
|     pub weapon_id: i16, |     pub weapon_id: i16, | ||||||
|  |     pub money: i32, | ||||||
| } | } | ||||||
| @@ -204,7 +204,9 @@ pub async fn run(radar_data: ArcRwlockRadarData, connector: Connector, pcileech_ | |||||||
|                             local_data.has_awp, |                             local_data.has_awp, | ||||||
|                             local_data.is_scoped, |                             local_data.is_scoped, | ||||||
|                             local_data.player_name, |                             local_data.player_name, | ||||||
|                             local_data.weapon_id |                             local_data.weapon_id, | ||||||
|  |                             local_data.money, | ||||||
|  |                             local_data.health | ||||||
|                         ) |                         ) | ||||||
|                     ) |                     ) | ||||||
|                 ); |                 ); | ||||||
| @@ -243,7 +245,9 @@ pub async fn run(radar_data: ArcRwlockRadarData, connector: Connector, pcileech_ | |||||||
|                                     player_data.has_awp, |                                     player_data.has_awp, | ||||||
|                                     player_data.is_scoped, |                                     player_data.is_scoped, | ||||||
|                                     player_data.player_name, |                                     player_data.player_name, | ||||||
|                                     player_data.weapon_id |                                     player_data.weapon_id, | ||||||
|  |                                     player_data.money, | ||||||
|  |                                     player_data.health | ||||||
|                                 ) |                                 ) | ||||||
|                             ) |                             ) | ||||||
|                         ); |                         ); | ||||||
|   | |||||||
							
								
								
									
										122
									
								
								src/websocket.rs
									
									
									
									
									
								
							
							
						
						
									
										122
									
								
								src/websocket.rs
									
									
									
									
									
								
							| @@ -1,20 +1,27 @@ | |||||||
| use std::{sync::Arc, path::PathBuf}; | use std::{sync::Arc, path::PathBuf, collections::HashMap}; | ||||||
|  |  | ||||||
| use axum::{ | use axum::{ | ||||||
|     extract::{ws::{WebSocketUpgrade, WebSocket, Message}, State}, |     extract::{ws::{WebSocketUpgrade, WebSocket, Message}, State}, | ||||||
|     response::Response, |     response::Response, | ||||||
|     routing::get, |     routing::get, | ||||||
|     Router, |     Router, | ||||||
| }; | }; | ||||||
|  | use flate2::{write::GzEncoder, Compression}; | ||||||
| use tokio::sync::RwLock; | use std::io::Write; | ||||||
|  | use tokio::sync::{RwLock, Mutex}; | ||||||
| use tower_http::services::ServeDir; | use tower_http::services::ServeDir; | ||||||
|  |  | ||||||
| use crate::comms::RadarData; | use crate::comms::{RadarData}; | ||||||
|  |  | ||||||
|  | struct ClientState { | ||||||
|  |     last_entity_count: usize, | ||||||
|  |     ping_ms: u32, | ||||||
|  |     high_latency: bool, | ||||||
|  | } | ||||||
|  |  | ||||||
| #[derive(Clone)] | #[derive(Clone)] | ||||||
| struct AppState { | struct AppState { | ||||||
|     data_lock: Arc<RwLock<RadarData>> |     data_lock: Arc<RwLock<RadarData>>, | ||||||
|  |     clients: Arc<Mutex<HashMap<String, ClientState>>>, | ||||||
| } | } | ||||||
|  |  | ||||||
| async fn ws_handler(ws: WebSocketUpgrade, State(state): State<AppState>) -> Response { | async fn ws_handler(ws: WebSocketUpgrade, State(state): State<AppState>) -> Response { | ||||||
| @@ -23,25 +30,81 @@ 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) { | ||||||
|  |     let client_id = uuid::Uuid::new_v4().to_string(); | ||||||
|  |  | ||||||
|  |     { | ||||||
|  |         let mut clients = state.clients.lock().await; | ||||||
|  |         clients.insert(client_id.clone(), ClientState { | ||||||
|  |             last_entity_count: 0, | ||||||
|  |             ping_ms: 0, | ||||||
|  |             high_latency: false, | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     let mut compression_buffer: Vec<u8> = Vec::with_capacity(65536); | ||||||
|  |     let mut frame_counter = 0; | ||||||
|  |     let mut skip_frames = false; | ||||||
|  |  | ||||||
|     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 let Ok(text) = msg.to_text() { |             if let Ok(text) = msg.to_text() { | ||||||
|                 if text == "requestInfo" { |                 if text == "requestInfo" { | ||||||
|                     let str = { |                     frame_counter += 1; | ||||||
|                         let data = state.data_lock.read().await; |                     if skip_frames && frame_counter % 2 != 0 { | ||||||
|  |                         continue; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|                         match serde_json::to_string(&*data) { |                     let radar_data = state.data_lock.read().await; | ||||||
|                             Ok(json) => json, |                     let mut clients = state.clients.lock().await; | ||||||
|                             Err(e) => { |                     let client_state = clients.get_mut(&client_id).unwrap(); | ||||||
|                                 log::error!("Could not serialize data into json: {}", e.to_string()); |  | ||||||
|                                 log::error!("Sending \"error\" instead"); |                     let entity_count = radar_data.get_entities().len(); | ||||||
|                                 "error".to_string() |  | ||||||
|                             }, |                     if entity_count > 5 && !skip_frames && client_state.ping_ms > 100 { | ||||||
|                         } |                         skip_frames = true; | ||||||
|  |                         log::info!("Enabling frame skipping for high latency client"); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     client_state.last_entity_count = entity_count; | ||||||
|  |  | ||||||
|  |                     let Ok(json) = serde_json::to_string(&*radar_data) else { | ||||||
|  |                         continue; | ||||||
|                     }; |                     }; | ||||||
|  |  | ||||||
|                     if socket.send(Message::Text(str)).await.is_err() { |                     compression_buffer.clear(); | ||||||
|                         return; |  | ||||||
|  |                     let compression_level = if json.len() > 20000 || client_state.high_latency { | ||||||
|  |                         Compression::best() | ||||||
|  |                     } else if json.len() > 5000 { | ||||||
|  |                         Compression::default() | ||||||
|  |                     } else { | ||||||
|  |                         Compression::fast() | ||||||
|  |                     }; | ||||||
|  |  | ||||||
|  |                     let mut encoder = GzEncoder::new(Vec::new(), compression_level); | ||||||
|  |                     if encoder.write_all(json.as_bytes()).is_ok() { | ||||||
|  |                         match encoder.finish() { | ||||||
|  |                             Ok(compressed) => { | ||||||
|  |                                 if compressed.len() < json.len() { | ||||||
|  |                                     let mut message = vec![0x01]; | ||||||
|  |                                     message.extend_from_slice(&compressed); | ||||||
|  |                                     let _ = socket.send(Message::Binary(message)).await; | ||||||
|  |                                 } else { | ||||||
|  |                                     let mut uncompressed = vec![0x00]; | ||||||
|  |                                     uncompressed.extend_from_slice(json.as_bytes()); | ||||||
|  |                                     let _ = socket.send(Message::Binary(uncompressed)).await; | ||||||
|  |                                 } | ||||||
|  |                             }, | ||||||
|  |                             Err(_) => { | ||||||
|  |                                 let mut uncompressed = vec![0x00]; | ||||||
|  |                                 uncompressed.extend_from_slice(json.as_bytes()); | ||||||
|  |                                 let _ = socket.send(Message::Binary(uncompressed)).await; | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } else { | ||||||
|  |                         let mut uncompressed = vec![0x00]; | ||||||
|  |                         uncompressed.extend_from_slice(json.as_bytes()); | ||||||
|  |                         let _ = socket.send(Message::Binary(uncompressed)).await; | ||||||
|                     } |                     } | ||||||
|                 } else if text == "toggleMoneyReveal" { |                 } else if text == "toggleMoneyReveal" { | ||||||
|                     let new_value = { |                     let new_value = { | ||||||
| @@ -56,24 +119,39 @@ async fn handle_socket(mut socket: WebSocket, state: AppState) { | |||||||
|                         "enabled": new_value |                         "enabled": new_value | ||||||
|                     }); |                     }); | ||||||
|  |  | ||||||
|                     if socket.send(Message::Text(response.to_string())).await.is_err() { |                     let _ = socket.send(Message::Text(response.to_string())).await; | ||||||
|                         return; |                 } else if text.starts_with("ping:") { | ||||||
|  |                     if let Some(ping_str) = text.strip_prefix("ping:") { | ||||||
|  |                         if let Ok(ping_ms) = ping_str.parse::<u32>() { | ||||||
|  |                             let mut clients = state.clients.lock().await; | ||||||
|  |                             if let Some(client) = clients.get_mut(&client_id) { | ||||||
|  |                                 client.ping_ms = ping_ms; | ||||||
|  |                                 client.high_latency = ping_ms > 100; | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|                     } |                     } | ||||||
|  |                     let _ = socket.send(Message::Text("pong".to_string())).await; | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             return; |             break; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |     let mut clients = state.clients.lock().await; | ||||||
|  |     clients.remove(&client_id); | ||||||
| } | } | ||||||
|  |  | ||||||
| pub async fn run(path: PathBuf, port: u16, data_lock: Arc<RwLock<RadarData>>) -> anyhow::Result<()> { | pub async fn run(path: PathBuf, port: u16, data_lock: Arc<RwLock<RadarData>>) -> anyhow::Result<()> { | ||||||
|     let app = Router::new() |     let app = Router::new() | ||||||
|         .nest_service("/", ServeDir::new(path)) |         .nest_service("/", ServeDir::new(path)) | ||||||
|         .route("/ws", get(ws_handler)) |         .route("/ws", get(ws_handler)) | ||||||
|         .with_state(AppState { data_lock }); |         .with_state(AppState { | ||||||
|  |             data_lock, | ||||||
|  |             clients: Arc::new(Mutex::new(HashMap::new())) | ||||||
|  |         }); | ||||||
|  |  | ||||||
|     let address = format!("0.0.0.0:{}", port); |     let address = format!("0.0.0.0:{}", port); | ||||||
|  |     log::info!("Starting WebSocket server on {}", address); | ||||||
|     let listener = tokio::net::TcpListener::bind(address).await?; |     let listener = tokio::net::TcpListener::bind(address).await?; | ||||||
|     axum::serve(listener, app.into_make_service()) |     axum::serve(listener, app.into_make_service()) | ||||||
|         .await?; |         .await?; | ||||||
|   | |||||||
| @@ -4,14 +4,20 @@ | |||||||
| <head> | <head> | ||||||
|     <meta charset="UTF-8"> |     <meta charset="UTF-8"> | ||||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"> |     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||||
|     <title>radarflow</title> |     <title>radar</title> | ||||||
|     <link href="styles.css" rel="stylesheet" type="text/css" /> |     <link href="styles.css" rel="stylesheet" type="text/css" /> | ||||||
|  |     <script src="https://cdnjs.cloudflare.com/ajax/libs/pako/2.1.0/pako.min.js"></script> | ||||||
| </head> | </head> | ||||||
|  |  | ||||||
| <body> | <body> | ||||||
|     <div id="canvasContainer"> |     <div id="canvasContainer"> | ||||||
|  |         <button id="showMenuBtn" onclick="toggleMenu(true)" style="display: none;">Show Menu</button> | ||||||
|         <div id="settingsHolder"> |         <div id="settingsHolder"> | ||||||
|             <div class="settings"> |             <div class="settings"> | ||||||
|  |                 <div> | ||||||
|  |                     <input type="checkbox" onclick="togglePerformanceMode()" id="performanceMode" /> | ||||||
|  |                     <label for="performanceMode">Performance Mode</label> | ||||||
|  |                 </div> | ||||||
|                 <div> |                 <div> | ||||||
|                     <input type="checkbox" onclick="toggleZoom()" id="zoomCheck" name="zoom" /> |                     <input type="checkbox" onclick="toggleZoom()" id="zoomCheck" name="zoom" /> | ||||||
|                     <label for="zoomCheck">Zoom</label> |                     <label for="zoomCheck">Zoom</label> | ||||||
| @@ -28,6 +34,19 @@ | |||||||
|                     <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="toggleDisplayMoney()" id="moneyDisplay" name="money-display" | ||||||
|  |                         checked /> | ||||||
|  |                     <label for="moneyDisplay">Display Money</label> | ||||||
|  |                 </div> | ||||||
|  |                 <div> | ||||||
|  |                     <input type="checkbox" onclick="toggleHealth()" id="healthCheck" name="health" checked /> | ||||||
|  |                     <label for="healthCheck">Display Health</label> | ||||||
|  |                 </div> | ||||||
|  |                 <div> | ||||||
|  |                     <input type="checkbox" onclick="toggleOffscreenIndicators()" id="offscreenCheck" name="offscreen" checked /> | ||||||
|  |                     <label for="offscreenCheck">Off-screen Indicators</label> | ||||||
|  |                 </div> | ||||||
|                 <div> |                 <div> | ||||||
|                     <input type="checkbox" onclick="toggleRotate()" id="rotateCheck" name="rotate" checked /> |                     <input type="checkbox" onclick="toggleRotate()" id="rotateCheck" name="rotate" checked /> | ||||||
|                     <label for="rotateCheck">Rotate Map</label> |                     <label for="rotateCheck">Rotate Map</label> | ||||||
| @@ -35,36 +54,54 @@ | |||||||
|                 <div> |                 <div> | ||||||
|                     <input type="checkbox" onclick="toggleCentered()" id="centerCheck" name="center" checked /> |                     <input type="checkbox" onclick="toggleCentered()" id="centerCheck" name="center" checked /> | ||||||
|                     <label for="centerCheck">Player Centered</label> |                     <label for="centerCheck">Player Centered</label> | ||||||
|  |                     <div id="zoomLevelContainer" style="margin-top: 5px; margin-left: 20px; display: none;"> | ||||||
|  |                         <label for="zoomLevelSlider">Zoom Level: </label> | ||||||
|  |                         <span id="zoomLevelValue">1.0</span><br> | ||||||
|  |                         <input type="range" id="zoomLevelSlider" min="1.0" max="5.0" step="0.1" value="1.0" | ||||||
|  |                             style="width: 100%; margin: 5px 0;" oninput="updateZoomLevel(this.value)"> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="player-focus"> | ||||||
|  |                     <label for="playerSelect">Focus Player:</label> | ||||||
|  |                     <select id="playerSelect" onchange="changePlayerFocus()"> | ||||||
|  |                         <option value="local">YOU</option> | ||||||
|  |                     </select> | ||||||
|  |                 </div> | ||||||
|  |                 <div id="sizeControlsContainer" | ||||||
|  |                     style="margin-top: 10px; padding: 5px; border-top: 1px solid rgba(255, 255, 255, 0.2);"> | ||||||
|  |                     <div class="size-control" style="margin-bottom: 8px;"> | ||||||
|  |                         <label for="textSizeSlider">Text Size: </label> | ||||||
|  |                         <span id="textSizeValue">1.0</span><br> | ||||||
|  |                         <input type="range" id="textSizeSlider" min="0.1" max="2.0" step="0.1" value="1.0" | ||||||
|  |                             style="width: 100%; margin: 5px 0;" oninput="updateTextSize(this.value)"> | ||||||
|  |                     </div> | ||||||
|  |                     <div class="size-control"> | ||||||
|  |                         <label for="entitySizeSlider">Player Size: </label> | ||||||
|  |                         <span id="entitySizeValue">1.0</span><br> | ||||||
|  |                         <input type="range" id="entitySizeSlider" min="0.5" max="2.0" step="0.1" value="1.0" | ||||||
|  |                             style="width: 100%; margin: 5px 0;" oninput="updateEntitySize(this.value)"> | ||||||
|  |                     </div> | ||||||
|  |                     <div class="size-control" style="margin-top: 8px;"> | ||||||
|  |                         <label for="healthBarSizeSlider">Health Bar Size: </label> | ||||||
|  |                         <span id="healthBarSizeValue">1.0</span><br> | ||||||
|  |                         <input type="range" id="healthBarSizeSlider" min="0.5" max="2.5" step="0.1" value="1.0" | ||||||
|  |                             style="width: 100%; margin: 5px 0;" oninput="updateHealthBarSize(this.value)"> | ||||||
|  |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
|  |  | ||||||
|                 <button id="showDangerousBtn" onclick="toggleDangerousOptions()">Show Dangerous Options</button> |                 <button id="showDangerousBtn" onclick="toggleDangerousOptions()">Show Dangerous Options</button> | ||||||
|  |  | ||||||
|                 <div class="dangerous-options" id="dangerousOptions"> |                 <div class="dangerous-options" id="dangerousOptions"> | ||||||
|                     <div> |                     <div> | ||||||
|                         <input type="checkbox" onclick="toggleMoneyReveal()" id="moneyReveal" name="money" /> |                         <input type="checkbox" onclick="toggleMoneyReveal()" id="moneyReveal" name="money" /> | ||||||
|                         <label for="moneyReveal">Money Reveal (Write Memory)</label> |                         <label for="moneyReveal">Money Reveal (Write Memory)</label> | ||||||
|                     </div> |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
|  |                 <button id="hideMenuBtn" onclick="toggleMenu(false)">Hide Menu</button> | ||||||
|             </div> |             </div> | ||||||
|         </div> |         </div> | ||||||
|         <canvas id="canvas"></canvas> |         <canvas id="canvas"></canvas> | ||||||
|     </div> |     </div> | ||||||
|     <script src="script.js"></script> |     <script src="script.js"></script> | ||||||
|  |     <script src="webstuff.js"></script> | ||||||
|     <script> |  | ||||||
|         function toggleDangerousOptions() { |  | ||||||
|             const dangerousSection = document.getElementById('dangerousOptions'); |  | ||||||
|             const button = document.getElementById('showDangerousBtn'); |  | ||||||
|  |  | ||||||
|             if (dangerousSection.classList.contains('revealed')) { |  | ||||||
|                 dangerousSection.classList.remove('revealed'); |  | ||||||
|                 button.textContent = 'Show Dangerous Options'; |  | ||||||
|             } else { |  | ||||||
|                 dangerousSection.classList.add('revealed'); |  | ||||||
|                 button.textContent = 'Hide Dangerous Options'; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     </script> |  | ||||||
| </body> | </body> | ||||||
|  |  | ||||||
| </html> | </html> | ||||||
							
								
								
									
										2005
									
								
								webradar/script.js
									
									
									
									
									
								
							
							
						
						
									
										2005
									
								
								webradar/script.js
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -23,6 +23,8 @@ body { | |||||||
| canvas { | canvas { | ||||||
|     width: 100%; |     width: 100%; | ||||||
|     height: 100%; |     height: 100%; | ||||||
|  |     image-rendering: -webkit-optimize-contrast; | ||||||
|  |     image-rendering: crisp-edges; | ||||||
| } | } | ||||||
|  |  | ||||||
| #settingsHolder { | #settingsHolder { | ||||||
| @@ -47,6 +49,25 @@ canvas { | |||||||
|     background-color: rgba(25, 25, 25, 0.7); |     background-color: rgba(25, 25, 25, 0.7); | ||||||
|     border-radius: 5px; |     border-radius: 5px; | ||||||
|     transition: opacity 0.3s ease; |     transition: opacity 0.3s ease; | ||||||
|  |     font-size: 14px; | ||||||
|  |     max-height: 90vh; | ||||||
|  |     overflow-y: auto; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @media (max-width: 768px) { | ||||||
|  |     #settingsHolder .settings { | ||||||
|  |         font-size: 16px; | ||||||
|  |         padding: 12px; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #settingsHolder .settings input[type="checkbox"] { | ||||||
|  |         transform: scale(1.2); | ||||||
|  |         margin-right: 8px; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #settingsHolder .settings>div { | ||||||
|  |         padding: 6px 0; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #settingsHolder:hover .settings { | #settingsHolder:hover .settings { | ||||||
| @@ -92,30 +113,183 @@ canvas { | |||||||
|     background-color: #8a0000; |     background-color: #8a0000; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .size-control { | ||||||
|  |     margin-bottom: 10px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .size-control label { | ||||||
|  |     display: inline-block; | ||||||
|  |     margin-bottom: 3px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | input[type="range"] { | ||||||
|  |     width: 100%; | ||||||
|  |     margin: 5px 0; | ||||||
|  |     -webkit-appearance: none; | ||||||
|  |     height: 6px; | ||||||
|  |     background: #555; | ||||||
|  |     border-radius: 3px; | ||||||
|  |     outline: none; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | input[type="range"]::-webkit-slider-thumb { | ||||||
|  |     -webkit-appearance: none; | ||||||
|  |     appearance: none; | ||||||
|  |     width: 16px; | ||||||
|  |     height: 16px; | ||||||
|  |     border-radius: 50%; | ||||||
|  |     background: #68a3e5; | ||||||
|  |     cursor: pointer; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | input[type="range"]::-moz-range-thumb { | ||||||
|  |     width: 16px; | ||||||
|  |     height: 16px; | ||||||
|  |     border-radius: 50%; | ||||||
|  |     background: #68a3e5; | ||||||
|  |     cursor: pointer; | ||||||
|  |     border: none; | ||||||
|  | } | ||||||
|  |  | ||||||
| @media (max-width: 600px), | @media (max-width: 600px), | ||||||
| (max-height: 600px) { | (max-height: 600px) { | ||||||
|     #settingsHolder { |     #settingsHolder { | ||||||
|         display: none; |         display: none; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     #showMenuBtn { | ||||||
|  |         display: block !important; | ||||||
|  |         font-size: 16px !important; | ||||||
|  |         padding: 8px 12px !important; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @media (max-width: 400px), | @media (max-width: 400px), | ||||||
| (max-height: 400px) { | (max-height: 400px) { | ||||||
|     #canvasContainer::before { |     #canvasContainer::before { | ||||||
|         content: 'settings'; |         content: ''; | ||||||
|         position: fixed; |         display: none; | ||||||
|         top: 10px; |     } | ||||||
|         left: 10px; |  | ||||||
|         background-color: rgba(25, 25, 25, 0.7); |     #showMenuBtn { | ||||||
|         color: white; |         padding: 6px 10px !important; | ||||||
|         width: 30px; |         font-size: 14px !important; | ||||||
|         height: 30px; |     } | ||||||
|         border-radius: 15px; | } | ||||||
|         display: flex; |  | ||||||
|         align-items: center; | #playerSelect { | ||||||
|         justify-content: center; |     background-color: #333; | ||||||
|         font-size: 16px; |     color: #fff; | ||||||
|         cursor: pointer; |     border: 1px solid #555; | ||||||
|         z-index: 101; |     padding: 5px; | ||||||
|  |     border-radius: 4px; | ||||||
|  |     margin-left: 5px; | ||||||
|  |     cursor: pointer; | ||||||
|  |     min-width: 150px; | ||||||
|  |     font-size: inherit; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #playerSelect:hover { | ||||||
|  |     background-color: #444; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #playerSelect option { | ||||||
|  |     background-color: #333; | ||||||
|  |     color: #fff; | ||||||
|  |     padding: 5px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #playerSelect option:hover { | ||||||
|  |     background-color: #444; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .player-focus { | ||||||
|  |     margin-top: 10px; | ||||||
|  |     display: flex; | ||||||
|  |     align-items: center; | ||||||
|  |     flex-wrap: wrap; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #hideMenuBtn { | ||||||
|  |     background-color: #333; | ||||||
|  |     color: white; | ||||||
|  |     border: none; | ||||||
|  |     padding: 8px 10px; | ||||||
|  |     margin-top: 10px; | ||||||
|  |     border-radius: 3px; | ||||||
|  |     cursor: pointer; | ||||||
|  |     font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | ||||||
|  |     transition: background-color 0.3s; | ||||||
|  |     width: 100%; | ||||||
|  |     font-size: inherit; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #hideMenuBtn:hover { | ||||||
|  |     background-color: #444; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #showMenuBtn { | ||||||
|  |     position: fixed; | ||||||
|  |     top: 10px; | ||||||
|  |     left: 10px; | ||||||
|  |     background-color: rgba(25, 25, 25, 0.9); | ||||||
|  |     color: white; | ||||||
|  |     border: none; | ||||||
|  |     padding: 8px 12px; | ||||||
|  |     border-radius: 15px; | ||||||
|  |     font-size: 16px; | ||||||
|  |     cursor: pointer; | ||||||
|  |     z-index: 101; | ||||||
|  |     font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | ||||||
|  |     transition: opacity 0.3s; | ||||||
|  |     opacity: 0.8; | ||||||
|  |     box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #showMenuBtn:hover { | ||||||
|  |     opacity: 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | input[type="checkbox"] { | ||||||
|  |     margin-right: 8px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | label { | ||||||
|  |     cursor: pointer; | ||||||
|  |     user-select: none; | ||||||
|  |     margin-bottom: 2px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .settings>div { | ||||||
|  |     margin-bottom: 5px; | ||||||
|  |     padding: 3px 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .touch-device input[type="checkbox"] { | ||||||
|  |     transform: scale(1.3); | ||||||
|  |     margin: 2px 10px 2px 2px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .touch-device input[type="range"]::-webkit-slider-thumb { | ||||||
|  |     width: 20px; | ||||||
|  |     height: 20px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .touch-device input[type="range"]::-moz-range-thumb { | ||||||
|  |     width: 20px; | ||||||
|  |     height: 20px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .touch-device .settings>div { | ||||||
|  |     padding: 6px 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @media (prefers-color-scheme: dark) { | ||||||
|  |     #settingsHolder .settings { | ||||||
|  |         background-color: rgba(15, 15, 15, 0.85); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #showMenuBtn { | ||||||
|  |         background-color: rgba(15, 15, 15, 0.9); | ||||||
|     } |     } | ||||||
| } | } | ||||||
							
								
								
									
										25
									
								
								webradar/webstuff.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								webradar/webstuff.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | function toggleDangerousOptions() { | ||||||
|  |     const dangerousSection = document.getElementById('dangerousOptions'); | ||||||
|  |     const button = document.getElementById('showDangerousBtn'); | ||||||
|  |  | ||||||
|  |     if (dangerousSection.classList.contains('revealed')) { | ||||||
|  |         dangerousSection.classList.remove('revealed'); | ||||||
|  |         button.textContent = 'Show Dangerous Options'; | ||||||
|  |     } else { | ||||||
|  |         dangerousSection.classList.add('revealed'); | ||||||
|  |         button.textContent = 'Hide Dangerous Options'; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function toggleMenu(show) { | ||||||
|  |     const settingsHolder = document.getElementById('settingsHolder'); | ||||||
|  |     const showMenuBtn = document.getElementById('showMenuBtn'); | ||||||
|  |  | ||||||
|  |     if (show) { | ||||||
|  |         settingsHolder.style.display = 'block'; | ||||||
|  |         showMenuBtn.style.display = 'none'; | ||||||
|  |     } else { | ||||||
|  |         settingsHolder.style.display = 'none'; | ||||||
|  |         showMenuBtn.style.display = 'block'; | ||||||
|  |     } | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user