empty session with firewall instead of suspend

This commit is contained in:
2025-12-12 03:52:40 -05:00
committed by sapphire
parent 54985d350a
commit ade95b5c65
3 changed files with 55 additions and 49 deletions
+48 -40
View File
@@ -1,14 +1,23 @@
use crate::util::{ use crate::util::{countdown::Countdown, system_info::SystemInfo};
consts::game::{EXE_ENHANCED, EXE_LEGACY}, use std::{
countdown::Countdown, error::Error,
system_info::SystemInfo, time::{Duration, Instant},
}; };
use std::time::{Duration, Instant}; use windows::{
use windows::Win32::{ Win32::{
Foundation::{HANDLE, NTSTATUS}, NetworkManagement::WindowsFirewall::{
System::Threading::{OpenProcess, PROCESS_SUSPEND_RESUME}, 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); const INTERVAL: Duration = Duration::from_secs(10);
#[derive(Debug)] #[derive(Debug)]
@@ -29,52 +38,51 @@ impl Default for EmptySession {
} }
impl EmptySession { impl EmptySession {
pub fn run_timers(&mut self, game_handle: &mut HANDLE) { pub fn run_timers(&mut self) -> Result<(), Box<dyn Error>> {
if self.disabled { if self.disabled {
self.countdown.count(); self.countdown.count();
} else { } else {
self.countdown.reset(); self.countdown.reset();
} }
if self.interval.elapsed() >= INTERVAL { if self.interval.elapsed() >= INTERVAL {
deactivate(game_handle); deactivate()?;
self.disabled = false; self.disabled = false;
} }
Ok(())
} }
} }
#[link(name = "ntdll")] pub fn activate(system_info: &mut SystemInfo) -> Result<bool, Box<dyn Error>> {
unsafe extern "system" { let Some(exe_path) = system_info.get_game_exe_path() else {
unsafe fn NtSuspendProcess(ProcessHandle: HANDLE) -> NTSTATUS; log::info!("wasn't able to find game exe");
unsafe fn NtResumeProcess(ProcessHandle: HANDLE) -> NTSTATUS; return Ok(false);
}
fn get_gta_pid(system_info: &mut SystemInfo) -> Option<u32> {
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;
}; };
match unsafe { OpenProcess(PROCESS_SUSPEND_RESUME, false, pid) } { for (direction, filter_name) in [
Ok(handle) => *game_handle = handle, (NET_FW_RULE_DIR_IN, FILTER_NAME_EMPTY_SESSION_IN),
Err(why) => { (NET_FW_RULE_DIR_OUT, FILTER_NAME_EMPTY_SESSION_OUT),
log::error!("failed to suspend game for empty session:\n{why}"); ] {
return false; 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) }?;
} }
} Ok(true)
unsafe { NtSuspendProcess(*game_handle) }.unwrap();
true
} }
pub fn deactivate(game_handle: &mut HANDLE) { pub fn deactivate() -> Result<(), Box<dyn Error>> {
if !game_handle.is_invalid() { let policy: INetFwPolicy2 =
// ignoring the return because this function behaves very weirdly unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER) }?;
let _ = unsafe { NtResumeProcess(*game_handle) }; 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(())
} }
+1 -1
View File
@@ -82,7 +82,7 @@ impl GameNetworking {
} }
pub fn block_exe(&mut self, system_info: &mut SystemInfo) -> Result<(), Box<dyn Error>> { pub fn block_exe(&mut self, system_info: &mut SystemInfo) -> Result<(), Box<dyn Error>> {
let Some(exe_path) = get_game_exe_path(system_info) else { let Some(exe_path) = system_info.get_game_exe_path() else {
return Ok(()); return Ok(());
}; };
Self::block_generic(Mode::EntireGame(exe_path.to_path_buf()))?; Self::block_generic(Mode::EntireGame(exe_path.to_path_buf()))?;
+6 -8
View File
@@ -49,7 +49,6 @@ pub struct App {
stage: Stage, stage: Stage,
pub flags: Flags, pub flags: Flags,
pub system_info: SystemInfo, pub system_info: SystemInfo,
game_handle: windows::Win32::Foundation::HANDLE,
pub anti_afk: features::anti_afk::AntiAfk, pub anti_afk: features::anti_afk::AntiAfk,
empty_session: features::empty_session::EmptySession, empty_session: features::empty_session::EmptySession,
force_close: features::force_close::ForceClose, force_close: features::force_close::ForceClose,
@@ -60,7 +59,7 @@ pub struct App {
impl eframe::App for App { impl eframe::App for App {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
ctx.request_repaint_after(Duration::from_millis(100)); 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") egui::TopBottomPanel::bottom("bottom_panel")
.exact_height(25.0) .exact_height(25.0)
.show(ctx, |ui| { .show(ctx, |ui| {
@@ -122,10 +121,7 @@ impl App {
ui.add_enabled_ui(!self.empty_session.disabled, |ui| { ui.add_enabled_ui(!self.empty_session.disabled, |ui| {
ui.horizontal(|ui| { ui.horizontal(|ui| {
if ui.button("Empty current session").clicked() if ui.button("Empty current session").clicked()
&& features::empty_session::activate( && features::empty_session::activate(&mut self.system_info).unwrap()
&mut self.game_handle,
&mut self.system_info,
)
{ {
self.empty_session.interval = Instant::now(); self.empty_session.interval = Instant::now();
self.empty_session.disabled = true; self.empty_session.disabled = true;
@@ -334,7 +330,9 @@ impl Drop for App {
settings: self.settings.clone(), settings: self.settings.clone(),
} }
.set(); .set();
// make sure we are not suspending game // make sure we are not network blocking game
features::empty_session::deactivate(&mut self.game_handle); if let Err(why) = features::empty_session::deactivate() {
log::error!("couldn't deactivate empty session: {why}");
}
} }
} }