4 Commits
7 changed files with 111 additions and 39 deletions
Generated
+1 -1
View File
@@ -1455,7 +1455,7 @@ dependencies = [
[[package]] [[package]]
name = "gta-tools" name = "gta-tools"
version = "0.5.0" version = "0.5.1"
dependencies = [ dependencies = [
"catppuccin-egui", "catppuccin-egui",
"chrono", "chrono",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "gta-tools" name = "gta-tools"
version = "0.5.0" version = "0.5.1"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
+2 -2
View File
@@ -1,4 +1,4 @@
# GTA Tools # GTA Tools
A toolset of convenient things for GTA 5 Online. A toolset of convenient things for GTA V Online.
![](https://i.vgy.me/hbOUEa.png) ![](https://i.vgy.me/s6bf1g.png)
+21 -15
View File
@@ -7,8 +7,8 @@ const INTERVAL: Duration = Duration::from_secs(3);
#[derive(Debug)] #[derive(Debug)]
pub struct ForceClose { pub struct ForceClose {
pub button_text: String, pub button_text: String,
prompting: bool, timer: Instant,
interval: Instant, counting: bool,
current_frame: bool, current_frame: bool,
} }
@@ -16,8 +16,8 @@ impl Default for ForceClose {
fn default() -> Self { fn default() -> Self {
Self { Self {
button_text: "Force close game".to_owned(), button_text: "Force close game".to_owned(),
prompting: false, timer: Instant::now(),
interval: Instant::now(), counting: false,
current_frame: false, current_frame: false,
} }
} }
@@ -25,22 +25,28 @@ impl Default for ForceClose {
impl ForceClose { impl ForceClose {
pub fn prompt(&mut self, force_close_button_clicked: bool, sysinfo: &mut System) { pub fn prompt(&mut self, force_close_button_clicked: bool, sysinfo: &mut System) {
if force_close_button_clicked && !self.prompting { if force_close_button_clicked && !self.counting {
*self = Self { self.button_text = "Are you sure?".to_owned();
button_text: "Are you sure?".to_owned(), self.timer = Instant::now();
prompting: true, self.counting = true;
interval: Instant::now(), self.current_frame = true;
current_frame: true,
}
} }
if self.prompting && self.interval.elapsed() <= INTERVAL { if self.counting && self.timer.elapsed() >= INTERVAL {
self.reset();
} else {
if force_close_button_clicked && !self.current_frame { if force_close_button_clicked && !self.current_frame {
activate(sysinfo); activate(sysinfo);
*self = Self::default(); self.reset();
} }
} else {
*self = Self::default();
} }
self.finish_current_frame();
}
fn reset(&mut self) {
*self = Self::default();
}
fn finish_current_frame(&mut self) {
if self.current_frame { if self.current_frame {
self.current_frame = false; self.current_frame = false;
} }
+77 -15
View File
@@ -1,5 +1,11 @@
use crate::util::consts::game::{EXE_ENHANCED, EXE_LEGACY}; use crate::util::consts::{
use std::path::Path; colours,
game::{EXE_ENHANCED, EXE_LEGACY},
};
use std::{
path::Path,
time::{Duration, Instant},
};
use sysinfo::System; use sysinfo::System;
use windows::{ use windows::{
Win32::{ Win32::{
@@ -13,28 +19,63 @@ use windows::{
CoUninitialize, CoUninitialize,
}, },
}, },
core::{BSTR, HRESULT}, core::BSTR,
}; };
const FILTER_NAME_IN: &str = "[GTA Tools] Block all inbound 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"; const FILTER_NAME_OUT: &str = "[GTA Tools] Block all outbound traffic for GTA V";
const INTERVAL: Duration = Duration::from_secs(3);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BlockedStatus {
Blocked,
Failed,
Unblocked,
}
impl From<bool> for BlockedStatus {
fn from(value: bool) -> Self {
match value {
true => Self::Blocked,
false => Self::Unblocked,
}
}
}
impl From<BlockedStatus> for eframe::egui::Color32 {
fn from(value: BlockedStatus) -> Self {
match value {
BlockedStatus::Blocked => colours::RED,
BlockedStatus::Failed => colours::YELLOW,
BlockedStatus::Unblocked => colours::GREEN,
}
}
}
impl BlockedStatus {
pub fn to_color32(&self) -> eframe::egui::Color32 {
(*self).into()
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct GameNetworking { pub struct GameNetworking {
pub is_blocked: bool,
com_initialized: bool, com_initialized: bool,
policy: INetFwPolicy2, pub blocked_status: BlockedStatus,
timer: Instant,
counting: bool,
} }
impl Default for GameNetworking { impl Default for GameNetworking {
fn default() -> Self { fn default() -> Self {
let result = unsafe { CoInitializeEx(None, COINIT_MULTITHREADED) };
let mut gn = Self { let mut gn = Self {
is_blocked: false, blocked_status: BlockedStatus::Unblocked,
com_initialized: result != HRESULT(0x80010106u32 as i32), com_initialized: unsafe { CoInitializeEx(None, COINIT_MULTITHREADED) }.is_ok(),
policy: unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER).unwrap() }, timer: Instant::now(),
counting: false,
}; };
gn.is_blocked = gn.is_blocked(); gn.blocked_status = gn.is_blocked().into();
gn gn
} }
} }
@@ -52,9 +93,12 @@ impl Drop for GameNetworking {
impl GameNetworking { impl GameNetworking {
pub fn block_all(&mut self, sysinfo: &mut System) { pub fn block_all(&mut self, sysinfo: &mut System) {
let Some(exe_path) = get_game_exe_path(sysinfo) else { let Some(exe_path) = get_game_exe_path(sysinfo) else {
self.blocked_status = BlockedStatus::Failed;
return; return;
}; };
let rules = unsafe { self.policy.Rules().unwrap() }; let policy: INetFwPolicy2 =
unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER).unwrap() };
let rules = unsafe { policy.Rules().unwrap() };
let filter_name_in = BSTR::from(FILTER_NAME_IN); let filter_name_in = BSTR::from(FILTER_NAME_IN);
let filter_name_out = BSTR::from(FILTER_NAME_OUT); let filter_name_out = BSTR::from(FILTER_NAME_OUT);
unsafe { unsafe {
@@ -84,11 +128,13 @@ impl GameNetworking {
outbound_rule.SetProtocol(NET_FW_IP_PROTOCOL_ANY.0).unwrap(); outbound_rule.SetProtocol(NET_FW_IP_PROTOCOL_ANY.0).unwrap();
rules.Add(&outbound_rule).unwrap(); rules.Add(&outbound_rule).unwrap();
} }
self.is_blocked = self.is_blocked(); self.blocked_status = self.is_blocked().into();
} }
pub fn unblock_all(&mut self) { pub fn unblock_all(&mut self) {
let rules = unsafe { self.policy.Rules().unwrap() }; let policy: INetFwPolicy2 =
unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER).unwrap() };
let rules = unsafe { policy.Rules().unwrap() };
unsafe { unsafe {
let result = rules.Remove(&BSTR::from(FILTER_NAME_IN)); let result = rules.Remove(&BSTR::from(FILTER_NAME_IN));
if let Err(ref why) = result { if let Err(ref why) = result {
@@ -103,15 +149,31 @@ impl GameNetworking {
} }
} }
} }
self.is_blocked = self.is_blocked(); self.blocked_status = self.is_blocked().into();
} }
fn is_blocked(&self) -> bool { fn is_blocked(&self) -> bool {
let rules = unsafe { self.policy.Rules().unwrap() }; let policy: INetFwPolicy2 =
unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER).unwrap() };
let rules = unsafe { policy.Rules().unwrap() };
let in_rule_exists = unsafe { rules.Item(&BSTR::from(FILTER_NAME_IN)).is_ok() }; 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() }; let out_rule_exists = unsafe { rules.Item(&BSTR::from(FILTER_NAME_OUT)).is_ok() };
in_rule_exists || out_rule_exists in_rule_exists || out_rule_exists
} }
pub fn if_failed_return_to_unblocked(&mut self) {
if self.blocked_status == BlockedStatus::Failed && !self.counting {
self.counting = true;
self.timer = Instant::now();
}
if self.blocked_status == BlockedStatus::Failed
&& self.counting
&& self.timer.elapsed() >= INTERVAL
{
self.counting = false;
self.blocked_status = BlockedStatus::Unblocked;
};
}
} }
fn get_game_exe_path(sysinfo: &mut System) -> Option<&Path> { fn get_game_exe_path(sysinfo: &mut System) -> Option<&Path> {
+2 -5
View File
@@ -156,15 +156,12 @@ impl App {
{ {
self.game_networking.unblock_all(); self.game_networking.unblock_all();
} }
let tint = match self.game_networking.is_blocked {
true => egui::Color32::from_hex("#f96554").unwrap(),
false => egui::Color32::from_hex("#68f954").unwrap(),
};
ui.add( ui.add(
egui::Image::new(egui::include_image!("../../assets/circle.svg")) egui::Image::new(egui::include_image!("../../assets/circle.svg"))
.max_size([4.0, 4.0].into()) .max_size([4.0, 4.0].into())
.tint(tint), .tint(self.game_networking.blocked_status.to_color32()),
); );
self.game_networking.if_failed_return_to_unblocked();
}); });
}); });
response.response.on_disabled_hover_text( response.response.on_disabled_hover_text(
+7
View File
@@ -8,3 +8,10 @@ pub mod game {
pub const EXE_LEGACY: &str = "GTA5.exe"; pub const EXE_LEGACY: &str = "GTA5.exe";
pub const WINDOW_TITLE: &str = "Grand Theft Auto V"; pub const WINDOW_TITLE: &str = "Grand Theft Auto V";
} }
pub mod colours {
use eframe::egui::Color32;
pub const RED: Color32 = Color32::from_rgb(249, 101, 84);
pub const YELLOW: Color32 = Color32::from_rgb(249, 236, 84);
pub const GREEN: Color32 = Color32::from_rgb(104, 249, 84);
}