diff --git a/src/features/empty_session.rs b/src/features/empty_session.rs index 1d27343..b286d09 100644 --- a/src/features/empty_session.rs +++ b/src/features/empty_session.rs @@ -1,14 +1,23 @@ -use crate::util::{ - consts::game::{EXE_ENHANCED, EXE_LEGACY}, - countdown::Countdown, - system_info::SystemInfo, +use crate::util::{countdown::Countdown, system_info::SystemInfo}; +use std::{ + error::Error, + time::{Duration, Instant}, }; -use std::time::{Duration, Instant}; -use windows::Win32::{ - Foundation::{HANDLE, NTSTATUS}, - System::Threading::{OpenProcess, PROCESS_SUSPEND_RESUME}, +use windows::{ + Win32::{ + NetworkManagement::WindowsFirewall::{ + INetFwPolicy2, INetFwRule, NET_FW_ACTION_BLOCK, NET_FW_IP_PROTOCOL_UDP, + NET_FW_RULE_DIR_IN, NET_FW_RULE_DIR_OUT, NetFwPolicy2, NetFwRule, + }, + System::Com::{CLSCTX_INPROC_SERVER, CoCreateInstance}, + }, + core::BSTR, }; +const FILTER_NAME_EMPTY_SESSION_IN: &str = "[GTA Tools] Block inbound UDP traffic for all of GTA V"; +const FILTER_NAME_EMPTY_SESSION_OUT: &str = + "[GTA Tools] Block outbound UDP traffic for all of GTA V"; + const INTERVAL: Duration = Duration::from_secs(10); #[derive(Debug)] @@ -29,52 +38,51 @@ impl Default for EmptySession { } impl EmptySession { - pub fn run_timers(&mut self, game_handle: &mut HANDLE) { + pub fn run_timers(&mut self) -> Result<(), Box> { if self.disabled { self.countdown.count(); } else { self.countdown.reset(); } if self.interval.elapsed() >= INTERVAL { - deactivate(game_handle); + deactivate()?; self.disabled = false; } + Ok(()) } } -#[link(name = "ntdll")] -unsafe extern "system" { - unsafe fn NtSuspendProcess(ProcessHandle: HANDLE) -> NTSTATUS; - unsafe fn NtResumeProcess(ProcessHandle: HANDLE) -> NTSTATUS; -} - -fn get_gta_pid(system_info: &mut SystemInfo) -> Option { - system_info.refresh(); - system_info - .processes() - .iter() - .find(|p| p.name() == EXE_ENHANCED || p.name() == EXE_LEGACY) - .map(|p| p.pid()) -} - -pub fn activate(game_handle: &mut HANDLE, system_info: &mut SystemInfo) -> bool { - let Some(pid) = get_gta_pid(system_info) else { - return false; +pub fn activate(system_info: &mut SystemInfo) -> Result> { + let Some(exe_path) = system_info.get_game_exe_path() else { + log::info!("wasn't able to find game exe"); + return Ok(false); }; - match unsafe { OpenProcess(PROCESS_SUSPEND_RESUME, false, pid) } { - Ok(handle) => *game_handle = handle, - Err(why) => { - log::error!("failed to suspend game for empty session:\n{why}"); - return false; - } + for (direction, filter_name) in [ + (NET_FW_RULE_DIR_IN, FILTER_NAME_EMPTY_SESSION_IN), + (NET_FW_RULE_DIR_OUT, FILTER_NAME_EMPTY_SESSION_OUT), + ] { + let policy: INetFwPolicy2 = + unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER) }?; + let rules = unsafe { policy.Rules() }?; + unsafe { rules.Remove(&BSTR::from(filter_name)) }?; + let rule: INetFwRule = unsafe { CoCreateInstance(&NetFwRule, None, CLSCTX_INPROC_SERVER) }?; + unsafe { rule.SetName(&BSTR::from(filter_name)) }?; + unsafe { rule.SetApplicationName(&BSTR::from(exe_path.to_string_lossy().to_string())) }?; + unsafe { rule.SetDirection(direction) }?; + unsafe { rule.SetEnabled(true.into()) }?; + unsafe { rule.SetAction(NET_FW_ACTION_BLOCK) }?; + unsafe { rule.SetProtocol(NET_FW_IP_PROTOCOL_UDP.0) }?; + unsafe { rules.Add(&rule) }?; } - unsafe { NtSuspendProcess(*game_handle) }.unwrap(); - true + Ok(true) } -pub fn deactivate(game_handle: &mut HANDLE) { - if !game_handle.is_invalid() { - // ignoring the return because this function behaves very weirdly - let _ = unsafe { NtResumeProcess(*game_handle) }; +pub fn deactivate() -> Result<(), Box> { + let policy: INetFwPolicy2 = + unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER) }?; + let rules = unsafe { policy.Rules() }?; + for filter_name in [FILTER_NAME_EMPTY_SESSION_IN, FILTER_NAME_EMPTY_SESSION_OUT] { + unsafe { rules.Remove(&BSTR::from(filter_name)) }?; } + Ok(()) } diff --git a/src/features/game_networking.rs b/src/features/game_networking.rs index 3b795b9..413f7cf 100644 --- a/src/features/game_networking.rs +++ b/src/features/game_networking.rs @@ -82,7 +82,7 @@ impl GameNetworking { } pub fn block_exe(&mut self, system_info: &mut SystemInfo) -> Result<(), Box> { - let Some(exe_path) = get_game_exe_path(system_info) else { + let Some(exe_path) = system_info.get_game_exe_path() else { return Ok(()); }; Self::block_generic(Mode::EntireGame(exe_path.to_path_buf()))?; diff --git a/src/gui/app.rs b/src/gui/app.rs index 234a20e..d93460b 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -49,7 +49,6 @@ pub struct App { stage: Stage, pub flags: Flags, pub system_info: SystemInfo, - game_handle: windows::Win32::Foundation::HANDLE, pub anti_afk: features::anti_afk::AntiAfk, empty_session: features::empty_session::EmptySession, force_close: features::force_close::ForceClose, @@ -60,7 +59,7 @@ pub struct App { impl eframe::App for App { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { ctx.request_repaint_after(Duration::from_millis(100)); - self.empty_session.run_timers(&mut self.game_handle); + self.empty_session.run_timers().unwrap(); egui::TopBottomPanel::bottom("bottom_panel") .exact_height(25.0) .show(ctx, |ui| { @@ -122,10 +121,7 @@ impl App { ui.add_enabled_ui(!self.empty_session.disabled, |ui| { ui.horizontal(|ui| { if ui.button("Empty current session").clicked() - && features::empty_session::activate( - &mut self.game_handle, - &mut self.system_info, - ) + && features::empty_session::activate(&mut self.system_info).unwrap() { self.empty_session.interval = Instant::now(); self.empty_session.disabled = true; @@ -334,7 +330,9 @@ impl Drop for App { settings: self.settings.clone(), } .set(); - // make sure we are not suspending game - features::empty_session::deactivate(&mut self.game_handle); + // make sure we are not network blocking game + if let Err(why) = features::empty_session::deactivate() { + log::error!("couldn't deactivate empty session: {why}"); + } } }