From 09f5a3895b1a133df7b9aeda5a9ef3c4d9f36509 Mon Sep 17 00:00:00 2001 From: futile Date: Sat, 19 Apr 2025 10:34:10 +0100 Subject: [PATCH] use windows api instead of `netsh` command to manage game's network access --- Cargo.toml | 1 + src/features/game_networking.rs | 166 +++++++++++++++++++++----------- src/gui/app.rs | 11 ++- 3 files changed, 119 insertions(+), 59 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 266980b..ec1c491 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ windows = { version = "0.61.1", features = [ "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging", "Win32_Security", + "Win32_NetworkManagement_WindowsFirewall", ] } winreg = "0.55.0" diff --git a/src/features/game_networking.rs b/src/features/game_networking.rs index 49b9d65..76d5369 100644 --- a/src/features/game_networking.rs +++ b/src/features/game_networking.rs @@ -1,9 +1,118 @@ use crate::util::consts::game::{EXE_ENHANCED, EXE_LEGACY}; -use std::{os::windows::process::CommandExt, path::Path, process::Command}; +use std::path::Path; use sysinfo::System; -use windows::Win32::System::Threading::CREATE_NO_WINDOW; +use windows::{ + Win32::{ + Foundation::E_INVALIDARG, + NetworkManagement::WindowsFirewall::{ + INetFwPolicy2, INetFwRule, NET_FW_ACTION_BLOCK, NET_FW_IP_PROTOCOL_ANY, + NET_FW_RULE_DIR_IN, NET_FW_RULE_DIR_OUT, NetFwPolicy2, NetFwRule, + }, + System::Com::{ + CLSCTX_INPROC_SERVER, COINIT_MULTITHREADED, CoCreateInstance, CoInitializeEx, + CoUninitialize, + }, + }, + core::{BSTR, HRESULT}, +}; -const FILTER_NAME: &str = "[GTA Tools] Block all traffic for GTA V"; +const FILTER_NAME_IN: &str = "[GTA Tools] Block all inbound traffic for GTA V"; +const FILTER_NAME_OUT: &str = "[GTA Tools] Block all outbound traffic for GTA V"; + +#[derive(Debug)] +pub struct GameNetworking { + pub is_blocked: bool, + com_initialized: bool, + policy: INetFwPolicy2, +} + +impl Default for GameNetworking { + fn default() -> Self { + let result = unsafe { CoInitializeEx(None, COINIT_MULTITHREADED) }; + let mut gn = Self { + is_blocked: false, + com_initialized: result != HRESULT(0x80010106u32 as i32), + policy: unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER).unwrap() }, + }; + gn.is_blocked = gn.is_blocked(); + gn + } +} + +impl Drop for GameNetworking { + fn drop(&mut self) { + unsafe { + if self.com_initialized { + CoUninitialize(); + } + } + } +} + +impl GameNetworking { + pub fn block_all(&mut self, sysinfo: &mut System) { + let Some(exe_path) = get_game_exe_path(sysinfo) else { + return; + }; + let rules = unsafe { self.policy.Rules().unwrap() }; + let filter_name_in = BSTR::from(FILTER_NAME_IN); + let filter_name_out = BSTR::from(FILTER_NAME_OUT); + unsafe { + let _ = rules.Remove(&filter_name_in); + let _ = rules.Remove(&filter_name_out); + } + let exe_path = BSTR::from(exe_path.to_string_lossy().to_string()); + unsafe { + let inbound_rule: INetFwRule = + CoCreateInstance(&NetFwRule, None, CLSCTX_INPROC_SERVER).unwrap(); + inbound_rule.SetName(&filter_name_in).unwrap(); + inbound_rule.SetApplicationName(&exe_path).unwrap(); + inbound_rule.SetDirection(NET_FW_RULE_DIR_IN).unwrap(); + inbound_rule.SetEnabled(true.into()).unwrap(); + inbound_rule.SetAction(NET_FW_ACTION_BLOCK).unwrap(); + inbound_rule.SetProtocol(NET_FW_IP_PROTOCOL_ANY.0).unwrap(); + rules.Add(&inbound_rule).unwrap(); + } + unsafe { + let outbound_rule: INetFwRule = + CoCreateInstance(&NetFwRule, None, CLSCTX_INPROC_SERVER).unwrap(); + outbound_rule.SetName(&filter_name_out).unwrap(); + outbound_rule.SetApplicationName(&exe_path).unwrap(); + outbound_rule.SetDirection(NET_FW_RULE_DIR_OUT).unwrap(); + outbound_rule.SetEnabled(true.into()).unwrap(); + outbound_rule.SetAction(NET_FW_ACTION_BLOCK).unwrap(); + outbound_rule.SetProtocol(NET_FW_IP_PROTOCOL_ANY.0).unwrap(); + rules.Add(&outbound_rule).unwrap(); + } + self.is_blocked = self.is_blocked(); + } + + pub fn unblock_all(&mut self) { + let rules = unsafe { self.policy.Rules().unwrap() }; + unsafe { + let result = rules.Remove(&BSTR::from(FILTER_NAME_IN)); + if let Err(ref why) = result { + if why.code() != E_INVALIDARG { + result.unwrap(); + } + } + let result = rules.Remove(&BSTR::from(FILTER_NAME_OUT)); + if let Err(ref why) = result { + if why.code() != E_INVALIDARG { + result.unwrap(); + } + } + } + self.is_blocked = self.is_blocked(); + } + + fn is_blocked(&self) -> bool { + let rules = unsafe { self.policy.Rules().unwrap() }; + let in_rule_exists = unsafe { rules.Item(&BSTR::from(FILTER_NAME_IN)).is_ok() }; + let out_rule_exists = unsafe { rules.Item(&BSTR::from(FILTER_NAME_OUT)).is_ok() }; + in_rule_exists || out_rule_exists + } +} fn get_game_exe_path(sysinfo: &mut System) -> Option<&Path> { sysinfo.refresh_all(); @@ -17,54 +126,3 @@ fn get_game_exe_path(sysinfo: &mut System) -> Option<&Path> { None } } - -pub fn block_all(sysinfo: &mut System) { - let Some(exe_path) = get_game_exe_path(sysinfo) else { - return; - }; - let exe_path = exe_path.display(); - Command::new("netsh") - .args([ - "advfirewall", - "firewall", - "add", - "rule", - &format!("name={FILTER_NAME}"), - "dir=out", - "action=block", - "protocol=ANY", - &format!("program={exe_path}"), - ]) - .creation_flags(CREATE_NO_WINDOW.0) - .spawn() - .unwrap(); - Command::new("netsh") - .args([ - "advfirewall", - "firewall", - "add", - "rule", - &format!("name={FILTER_NAME}"), - "dir=in", - "action=block", - "protocol=ANY", - &format!("program={exe_path}"), - ]) - .creation_flags(CREATE_NO_WINDOW.0) - .spawn() - .unwrap(); -} - -pub fn unblock_all() { - Command::new("netsh") - .args([ - "advfirewall", - "firewall", - "delete", - "rule", - &format!("name={FILTER_NAME}"), - ]) - .creation_flags(CREATE_NO_WINDOW.0) - .spawn() - .unwrap(); -} diff --git a/src/gui/app.rs b/src/gui/app.rs index 7580865..d098ba4 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -32,10 +32,11 @@ pub struct App { pub flags: Flags, pub sysinfo: sysinfo::System, game_handle: windows::Win32::Foundation::HANDLE, - pub launch: features::launch::Launch, - force_close: features::force_close::ForceClose, - empty_session: features::empty_session::EmptySession, pub anti_afk: features::anti_afk::AntiAfk, + empty_session: features::empty_session::EmptySession, + force_close: features::force_close::ForceClose, + game_networking: features::game_networking::GameNetworking, + pub launch: features::launch::Launch, } impl eframe::App for App { @@ -147,13 +148,13 @@ impl App { .add_sized([button_width, 18.0], egui::Button::new("Block")) .clicked() { - features::game_networking::block_all(&mut self.sysinfo); + self.game_networking.block_all(&mut self.sysinfo); } if ui .add_sized([button_width, 18.0], egui::Button::new("Unblock")) .clicked() { - features::game_networking::unblock_all(); + self.game_networking.unblock_all(); } }); });