20 Commits
Author SHA1 Message Date
futile 202554e03b 0.5.1 2025-04-20 01:47:16 +01:00
futile da2b15a0b6 redesign features::force_close internals a bit 2025-04-20 01:42:06 +01:00
futile ccc27b1944 slightly tweak features::game_networking design & add visual indicator for blocking failing due to game being closed 2025-04-20 01:25:25 +01:00
futile 10168e84d1 update readme 2025-04-19 10:52:20 +01:00
futile e72964a9f8 0.5.0 2025-04-19 10:37:06 +01:00
futile 18052d0c78 add red/green circle indicator beside Block/Unblock buttons 2025-04-19 10:35:47 +01:00
futile 09f5a3895b use windows api instead of netsh command to manage game's network access 2025-04-19 10:34:10 +01:00
futile 89892ef592 change a couple of imports in gui::app 2025-04-19 07:20:49 +01:00
futile 61de3aeee3 move debug viewport code to own module 2025-04-19 06:48:56 +01:00
futile 4ed00e6ac3 don't assign PersistentState to variable when setting 2025-04-19 05:23:11 +01:00
futile 332db0ea03 document the process of gui::run::app_creator with comments 2025-04-19 05:18:04 +01:00
futile 64d13e3964 move EmptySession::run_timers back into main update loop so it actually runs 2025-04-19 05:02:24 +01:00
futile feb3a793d9 redesign how anti afk activates 2025-04-19 04:54:02 +01:00
futile 585f454bc0 hunt down some unused pubs 2025-04-19 04:45:47 +01:00
futile 27524e855a numerous improvements in gui and feature code 2025-04-19 04:28:24 +01:00
futile 0257834d04 add checkbox to debug to spoof new version of the app beign available 2025-04-18 20:32:39 +01:00
futile 64e94bf790 change how vks are stored 2025-04-18 05:26:28 +01:00
futile 668a6eb38b get rid of clippy-related allows 2025-04-18 04:44:11 +01:00
futile 0de79769d2 reorganize some consts 2025-04-18 03:17:40 +01:00
futile b79c5f62b3 update screenshot in readme 2025-04-18 03:09:14 +01:00
19 changed files with 662 additions and 349 deletions
Generated
+158 -4
View File
@@ -424,6 +424,12 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "base64"
version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.22.1" version = "0.22.1"
@@ -779,6 +785,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991"
[[package]]
name = "data-url"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a"
[[package]] [[package]]
name = "deranged" name = "deranged"
version = "0.4.0" version = "0.4.0"
@@ -981,6 +993,7 @@ dependencies = [
"log", "log",
"mime_guess2", "mime_guess2",
"profiling", "profiling",
"resvg",
] ]
[[package]] [[package]]
@@ -1150,6 +1163,12 @@ dependencies = [
"miniz_oxide", "miniz_oxide",
] ]
[[package]]
name = "float-cmp"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
[[package]] [[package]]
name = "fnv" name = "fnv"
version = "1.0.7" version = "1.0.7"
@@ -1436,7 +1455,7 @@ dependencies = [
[[package]] [[package]]
name = "gta-tools" name = "gta-tools"
version = "0.4.2" version = "0.5.1"
dependencies = [ dependencies = [
"catppuccin-egui", "catppuccin-egui",
"chrono", "chrono",
@@ -1692,6 +1711,12 @@ dependencies = [
"tiff", "tiff",
] ]
[[package]]
name = "imagesize"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284"
[[package]] [[package]]
name = "immutable-chunkmap" name = "immutable-chunkmap"
version = "2.0.6" version = "2.0.6"
@@ -1801,6 +1826,15 @@ version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
[[package]]
name = "kurbo"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd85a5776cd9500c2e2059c8c76c3b01528566b7fcbaf8098b55a33fc298849b"
dependencies = [
"arrayvec",
]
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.171" version = "0.2.171"
@@ -2493,10 +2527,16 @@ version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
dependencies = [ dependencies = [
"siphasher", "siphasher 1.0.1",
"unicase", "unicase",
] ]
[[package]]
name = "pico-args"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
[[package]] [[package]]
name = "pin-project" name = "pin-project"
version = "1.1.10" version = "1.1.10"
@@ -2683,6 +2723,12 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
[[package]]
name = "rctree"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b42e27ef78c35d3998403c1d26f3efd9e135d3e5121b0a4845cc5cc27547f4f"
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.4.1" version = "0.4.1"
@@ -2718,6 +2764,29 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
[[package]]
name = "resvg"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cadccb3d99a9efb8e5e00c16fbb732cbe400db2ec7fc004697ee7d97d86cf1f4"
dependencies = [
"log",
"pico-args",
"rgb",
"svgtypes",
"tiny-skia",
"usvg",
]
[[package]]
name = "rgb"
version = "0.8.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a"
dependencies = [
"bytemuck",
]
[[package]] [[package]]
name = "ring" name = "ring"
version = "0.17.14" version = "0.17.14"
@@ -2732,6 +2801,12 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "roxmltree"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f"
[[package]] [[package]]
name = "rustc-hash" name = "rustc-hash"
version = "1.1.0" version = "1.1.0"
@@ -2941,6 +3016,21 @@ version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "simplecss"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c"
dependencies = [
"log",
]
[[package]]
name = "siphasher"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
[[package]] [[package]]
name = "siphasher" name = "siphasher"
version = "1.0.1" version = "1.0.1"
@@ -3048,6 +3138,9 @@ name = "strict-num"
version = "0.1.1" version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
dependencies = [
"float-cmp",
]
[[package]] [[package]]
name = "strum" name = "strum"
@@ -3099,6 +3192,16 @@ version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "svgtypes"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e44e288cd960318917cbd540340968b90becc8bc81f171345d706e7a89d9d70"
dependencies = [
"kurbo",
"siphasher 0.3.11",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.100" version = "2.0.100"
@@ -3248,6 +3351,7 @@ dependencies = [
"bytemuck", "bytemuck",
"cfg-if", "cfg-if",
"log", "log",
"png",
"tiny-skia-path", "tiny-skia-path",
] ]
@@ -3411,7 +3515,7 @@ version = "3.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7a3e9af6113ecd57b8c63d3cd76a385b2e3881365f1f489e54f49801d0c83ea" checksum = "b7a3e9af6113ecd57b8c63d3cd76a385b2e3881365f1f489e54f49801d0c83ea"
dependencies = [ dependencies = [
"base64", "base64 0.22.1",
"cookie_store", "cookie_store",
"flate2", "flate2",
"log", "log",
@@ -3432,7 +3536,7 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fadf18427d33828c311234884b7ba2afb57143e6e7e69fda7ee883b624661e36" checksum = "fadf18427d33828c311234884b7ba2afb57143e6e7e69fda7ee883b624661e36"
dependencies = [ dependencies = [
"base64", "base64 0.22.1",
"http", "http",
"httparse", "httparse",
"log", "log",
@@ -3449,6 +3553,50 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "usvg"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b0a51b72ab80ca511d126b77feeeb4fb1e972764653e61feac30adc161a756"
dependencies = [
"base64 0.21.7",
"log",
"pico-args",
"usvg-parser",
"usvg-tree",
"xmlwriter",
]
[[package]]
name = "usvg-parser"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bd4e3c291f45d152929a31f0f6c819245e2921bfd01e7bd91201a9af39a2bdc"
dependencies = [
"data-url",
"flate2",
"imagesize",
"kurbo",
"log",
"roxmltree",
"simplecss",
"siphasher 0.3.11",
"svgtypes",
"usvg-tree",
]
[[package]]
name = "usvg-tree"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ee3d202ebdb97a6215604b8f5b4d6ef9024efd623cf2e373a6416ba976ec7d3"
dependencies = [
"rctree",
"strict-num",
"svgtypes",
"tiny-skia-path",
]
[[package]] [[package]]
name = "utf-8" name = "utf-8"
version = "0.7.6" version = "0.7.6"
@@ -4409,6 +4557,12 @@ version = "0.8.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4"
[[package]]
name = "xmlwriter"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
[[package]] [[package]]
name = "yoke" name = "yoke"
version = "0.7.5" version = "0.7.5"
+3 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "gta-tools" name = "gta-tools"
version = "0.4.2" version = "0.5.1"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
@@ -10,7 +10,7 @@ catppuccin-egui = { version = "5.5.0", default-features = false, features = [
chrono = "0.4.40" chrono = "0.4.40"
dirs = "6.0.0" dirs = "6.0.0"
eframe = "0.31.1" eframe = "0.31.1"
egui_extras = { version = "0.31.1", features = ["image"] } egui_extras = { version = "0.31.1", features = ["image", "svg"] }
image = { version = "0.25.6", default-features = false, features = ["png"] } image = { version = "0.25.6", default-features = false, features = ["png"] }
open = "5.3.2" open = "5.3.2"
semver = "1.0.26" semver = "1.0.26"
@@ -25,6 +25,7 @@ windows = { version = "0.61.1", features = [
"Win32_UI_Shell", "Win32_UI_Shell",
"Win32_UI_WindowsAndMessaging", "Win32_UI_WindowsAndMessaging",
"Win32_Security", "Win32_Security",
"Win32_NetworkManagement_WindowsFirewall",
] } ] }
winreg = "0.55.0" winreg = "0.55.0"
+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/n6CrZi.png) ![](https://i.vgy.me/s6bf1g.png)
+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="512"
height="512"
viewBox="0 0 512 512"
version="1.1"
id="svg1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<g
id="layer1">
<circle
style="fill:#ffffff"
id="path1"
cx="256"
cy="256"
r="256" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 458 B

+29 -20
View File
@@ -1,13 +1,12 @@
#![allow(clippy::cast_possible_truncation)] use crate::util::{self, consts::game::WINDOW_TITLE};
use crate::util::{self, consts::GTA_WINDOW_TITLE};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use windows::Win32::UI::Input::KeyboardAndMouse::{ use windows::Win32::UI::Input::KeyboardAndMouse::{
KEYBD_EVENT_FLAGS, MAP_VIRTUAL_KEY_TYPE, MapVirtualKeyW, VK_NUMPAD4, VK_NUMPAD6, keybd_event, KEYBD_EVENT_FLAGS, MAP_VIRTUAL_KEY_TYPE, MapVirtualKeyW, VIRTUAL_KEY, VK_NUMPAD4, VK_NUMPAD6,
keybd_event,
}; };
pub const INTERVAL: Duration = Duration::from_secs(60); const INTERVAL: Duration = Duration::from_secs(60);
const PRESS_KEYS: [u8; 2] = [VK_NUMPAD4.0 as u8, VK_NUMPAD6.0 as u8]; const PRESS_KEYS: [VIRTUAL_KEY; 2] = [VK_NUMPAD4, VK_NUMPAD6];
#[derive(Debug)] #[derive(Debug)]
pub struct AntiAfk { pub struct AntiAfk {
@@ -25,30 +24,40 @@ impl Default for AntiAfk {
} }
impl AntiAfk { impl AntiAfk {
pub fn activate(&mut self) { pub fn can_activate(&self) -> bool {
if can_activate() { use util::win::{is_any_key_pressed, is_cursor_visible, is_window_focused};
send(&PRESS_KEYS); is_window_focused(WINDOW_TITLE) && !is_any_key_pressed(&PRESS_KEYS) && !is_cursor_visible()
} }
pub fn should_activate(&self) -> bool {
self.enabled && self.interval.elapsed() >= INTERVAL
}
pub fn activate(&mut self) {
send(&PRESS_KEYS);
self.interval = Instant::now(); self.interval = Instant::now();
} }
} }
pub fn can_activate() -> bool { fn send(vk_codes: &[VIRTUAL_KEY]) {
use util::win::{is_any_key_pressed, is_cursor_visible, is_window_focused}; vk_codes.iter().for_each(|vk_code| unsafe {
is_window_focused(GTA_WINDOW_TITLE) && !is_any_key_pressed(&PRESS_KEYS) && !is_cursor_visible()
}
pub fn send(vk_codes: &[u8]) {
vk_codes.iter().for_each(|vk_code: &u8| unsafe {
keybd_event( keybd_event(
*vk_code, vk_code.0 as u8,
u8::try_from(MapVirtualKeyW(u32::from(*vk_code), MAP_VIRTUAL_KEY_TYPE(0))).unwrap(), u8::try_from(MapVirtualKeyW(
u32::from(vk_code.0),
MAP_VIRTUAL_KEY_TYPE(0),
))
.unwrap(),
KEYBD_EVENT_FLAGS(0), KEYBD_EVENT_FLAGS(0),
0, 0,
); );
keybd_event( keybd_event(
*vk_code, vk_code.0 as u8,
u8::try_from(MapVirtualKeyW(u32::from(*vk_code), MAP_VIRTUAL_KEY_TYPE(0))).unwrap(), u8::try_from(MapVirtualKeyW(
u32::from(vk_code.0),
MAP_VIRTUAL_KEY_TYPE(0),
))
.unwrap(),
KEYBD_EVENT_FLAGS(2), KEYBD_EVENT_FLAGS(2),
0, 0,
); );
+26 -12
View File
@@ -1,5 +1,5 @@
use crate::util::{ use crate::util::{
consts::{ENHANCED, LEGACY}, consts::game::{EXE_ENHANCED, EXE_LEGACY},
countdown::Countdown, countdown::Countdown,
}; };
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
@@ -9,7 +9,7 @@ use windows::Win32::{
System::Threading::{OpenProcess, PROCESS_SUSPEND_RESUME}, System::Threading::{OpenProcess, PROCESS_SUSPEND_RESUME},
}; };
pub const INTERVAL: Duration = Duration::from_secs(10); const INTERVAL: Duration = Duration::from_secs(10);
#[derive(Debug)] #[derive(Debug)]
pub struct EmptySession { pub struct EmptySession {
@@ -28,10 +28,24 @@ impl Default for EmptySession {
} }
} }
impl EmptySession {
pub fn run_timers(&mut self, game_handle: &mut HANDLE) {
if self.disabled {
self.countdown.count();
} else {
self.countdown.reset();
}
if self.interval.elapsed() >= INTERVAL {
deactivate(game_handle);
self.disabled = false;
}
}
}
#[link(name = "ntdll")] #[link(name = "ntdll")]
unsafe extern "system" { unsafe extern "system" {
pub unsafe fn NtSuspendProcess(ProcessHandle: HANDLE) -> NTSTATUS; unsafe fn NtSuspendProcess(ProcessHandle: HANDLE) -> NTSTATUS;
pub unsafe fn NtResumeProcess(ProcessHandle: HANDLE) -> NTSTATUS; unsafe fn NtResumeProcess(ProcessHandle: HANDLE) -> NTSTATUS;
} }
fn get_gta_pid(sysinfo: &mut System) -> u32 { fn get_gta_pid(sysinfo: &mut System) -> u32 {
@@ -39,29 +53,29 @@ fn get_gta_pid(sysinfo: &mut System) -> u32 {
if let Some((pid, _)) = sysinfo if let Some((pid, _)) = sysinfo
.processes() .processes()
.iter() .iter()
.find(|(_, p)| p.name() == ENHANCED || p.name() == LEGACY) .find(|(_, p)| p.name() == EXE_ENHANCED || p.name() == EXE_LEGACY)
{ {
return pid.as_u32(); return pid.as_u32();
} }
u32::MAX u32::MAX
} }
pub fn activate(handle: &mut HANDLE, sysinfo: &mut System) { pub fn activate(game_handle: &mut HANDLE, sysinfo: &mut System) {
let pid = get_gta_pid(sysinfo); let pid = get_gta_pid(sysinfo);
if pid == u32::MAX { if pid == u32::MAX {
return; return;
} }
unsafe { unsafe {
*handle = OpenProcess(PROCESS_SUSPEND_RESUME, false, pid).unwrap(); *game_handle = OpenProcess(PROCESS_SUSPEND_RESUME, false, pid).unwrap();
let _ = NtSuspendProcess(*handle); let _ = NtSuspendProcess(*game_handle);
} }
} }
pub fn deactivate(handle: &mut HANDLE) { pub fn deactivate(game_handle: &mut HANDLE) {
unsafe { unsafe {
if !handle.is_invalid() { if !game_handle.is_invalid() {
let _ = NtResumeProcess(*handle); let _ = NtResumeProcess(*game_handle);
let _ = CloseHandle(*handle); let _ = CloseHandle(*game_handle);
} }
} }
} }
+37 -11
View File
@@ -1,38 +1,64 @@
use crate::util::consts::{ENHANCED, LEGACY}; use crate::util::consts::game::{EXE_ENHANCED, EXE_LEGACY};
use std::time::Instant; use std::time::{Duration, Instant};
use sysinfo::System; use sysinfo::System;
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,
pub prompting: bool, timer: Instant,
pub interval: Instant, counting: bool,
current_frame: bool,
} }
impl Default for ForceClose { 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,
} }
} }
} }
impl ForceClose { impl ForceClose {
pub fn prompting(&mut self) { pub fn prompt(&mut self, force_close_button_clicked: bool, sysinfo: &mut System) {
if force_close_button_clicked && !self.counting {
self.button_text = "Are you sure?".to_owned(); self.button_text = "Are you sure?".to_owned();
self.prompting = true; self.timer = Instant::now();
self.interval = Instant::now(); self.counting = true;
self.current_frame = true;
}
if self.counting && self.timer.elapsed() >= INTERVAL {
self.reset();
} else {
if force_close_button_clicked && !self.current_frame {
activate(sysinfo);
self.reset();
}
}
self.finish_current_frame();
}
fn reset(&mut self) {
*self = Self::default();
}
fn finish_current_frame(&mut self) {
if self.current_frame {
self.current_frame = false;
}
} }
} }
pub fn activate(sysinfo: &mut System) { fn activate(sysinfo: &mut System) {
sysinfo.refresh_all(); sysinfo.refresh_all();
sysinfo sysinfo
.processes() .processes()
.iter() .iter()
.filter(|(_, p)| p.name() == ENHANCED || p.name() == LEGACY) .filter(|(_, p)| p.name() == EXE_ENHANCED || p.name() == EXE_LEGACY)
.for_each(|(_, p)| { .for_each(|(_, p)| {
p.kill(); p.kill();
}); });
+176 -58
View File
@@ -1,72 +1,190 @@
#![allow(clippy::zombie_processes)] use crate::util::consts::{
colours,
use crate::util::consts::{ENHANCED, LEGACY}; game::{EXE_ENHANCED, EXE_LEGACY},
use std::{os::windows::process::CommandExt, path::Path, process::Command}; };
use std::{
path::Path,
time::{Duration, Instant},
};
use sysinfo::System; 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,
};
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";
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)]
pub struct GameNetworking {
com_initialized: bool,
pub blocked_status: BlockedStatus,
timer: Instant,
counting: bool,
}
impl Default for GameNetworking {
fn default() -> Self {
let mut gn = Self {
blocked_status: BlockedStatus::Unblocked,
com_initialized: unsafe { CoInitializeEx(None, COINIT_MULTITHREADED) }.is_ok(),
timer: Instant::now(),
counting: false,
};
gn.blocked_status = gn.is_blocked().into();
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 {
self.blocked_status = BlockedStatus::Failed;
return;
};
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_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.blocked_status = self.is_blocked().into();
}
pub fn unblock_all(&mut self) {
let policy: INetFwPolicy2 =
unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER).unwrap() };
let rules = unsafe { 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.blocked_status = self.is_blocked().into();
}
fn is_blocked(&self) -> bool {
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 out_rule_exists = unsafe { rules.Item(&BSTR::from(FILTER_NAME_OUT)).is_ok() };
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> {
sysinfo.refresh_all(); sysinfo.refresh_all();
if let Some((_, process)) = sysinfo if let Some((_, process)) = sysinfo
.processes() .processes()
.iter() .iter()
.find(|(_, p)| p.name() == ENHANCED || p.name() == LEGACY) .find(|(_, p)| p.name() == EXE_ENHANCED || p.name() == EXE_LEGACY)
{ {
process.exe() process.exe()
} else { } else {
None 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();
}
+5 -14
View File
@@ -1,27 +1,18 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{fmt::Display, path::PathBuf, process::Command}; use std::{path::PathBuf, process::Command};
use strum::EnumIter; use strum::{Display, EnumIter};
use winreg::{RegKey, enums::HKEY_LOCAL_MACHINE}; use winreg::{RegKey, enums::HKEY_LOCAL_MACHINE};
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Serialize, Deserialize, EnumIter)] #[derive(Clone, Copy, Default, Debug, Display, PartialEq, Eq, Serialize, Deserialize, EnumIter)]
pub enum Platform { pub enum Platform {
#[default] #[default]
Steam, Steam,
#[strum(to_string = "Rockstar Games")]
Rockstar, Rockstar,
#[strum(to_string = "Epic Games")]
Epic, Epic,
} }
impl Display for Platform {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let x = match self {
Self::Steam => "Steam",
Self::Rockstar => "Rockstar Games",
Self::Epic => "Epic Games",
};
write!(f, "{x}")
}
}
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Launch { pub struct Launch {
pub selected: Platform, pub selected: Platform,
+71 -178
View File
@@ -1,92 +1,60 @@
use crate::{ use crate::{
features::{ features,
self, anti_afk::AntiAfk, empty_session::EmptySession, force_close::ForceClose, gui::{settings::Settings, tools},
launch::Launch, util::{consts::game::WINDOW_TITLE, meta::Meta, persistent_state::PersistentState, win},
},
gui::{
settings::{self, Settings},
tools,
},
util::{
self,
consts::{APP_STORAGE_PATH, ENHANCED, GTA_WINDOW_TITLE, LEGACY},
meta::Meta,
persistent_state::PersistentState,
},
}; };
use eframe::egui; use eframe::egui;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use strum::{Display, EnumIter};
pub const WINDOW_SIZE: [f32; 2] = [240.0, 240.0]; pub const WINDOW_SIZE: [f32; 2] = [240.0, 240.0];
#[derive(Debug, Default, PartialEq, Eq)] #[derive(Clone, Copy, Debug, Default, Display, PartialEq, Eq, EnumIter)]
pub enum Stage { enum Stage {
#[default] #[default]
Main, Main,
Settings, Settings,
About, About,
} }
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Flags { pub struct Flags {
pub elevated: bool, pub elevated: bool,
debug: bool, pub debug: bool,
closing: bool, closing: bool,
current_frame: bool,
} }
#[derive(Debug)] #[derive(Debug, Default)]
pub struct App { pub struct App {
meta: Meta, pub meta: Meta,
pub settings: Settings, pub settings: Settings,
stage: Stage, stage: Stage,
pub flags: Flags, pub flags: Flags,
pub sysinfo: sysinfo::System, pub sysinfo: sysinfo::System,
pub game_handle: windows::Win32::Foundation::HANDLE, game_handle: windows::Win32::Foundation::HANDLE,
pub launch: Launch, pub anti_afk: features::anti_afk::AntiAfk,
force_close: ForceClose, empty_session: features::empty_session::EmptySession,
empty_session: EmptySession, force_close: features::force_close::ForceClose,
anti_afk: AntiAfk, game_networking: features::game_networking::GameNetworking,
} pub launch: features::launch::Launch,
impl Default for App {
fn default() -> Self {
Self {
meta: Meta::default(),
settings: Settings::default(),
stage: Stage::default(),
flags: Flags::default(),
sysinfo: sysinfo::System::new_all(),
game_handle: windows::Win32::Foundation::HANDLE::default(),
launch: Launch::default(),
force_close: ForceClose::default(),
empty_session: EmptySession::default(),
anti_afk: AntiAfk::default(),
}
}
} }
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) {
catppuccin_egui::set_theme(ctx, self.settings.theme.into()); ctx.request_repaint_after(Duration::from_millis(100));
self.run_timers(); self.empty_session.run_timers(&mut self.game_handle);
egui::TopBottomPanel::bottom("bottom_panel") egui::TopBottomPanel::bottom("bottom_panel")
.exact_height(25.0) .exact_height(25.0)
.show(ctx, |ui| { .show(ctx, |ui| {
ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| { ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| {
ui.selectable_value(&mut self.stage, Stage::Main, "Main"); tools::build_menu::<Stage>(ui, &mut self.stage);
ui.selectable_value(&mut self.stage, Stage::Settings, "Settings");
ui.selectable_value(&mut self.stage, Stage::About, "About");
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
let button = ui let button = ui
.add_enabled(!self.flags.elevated, egui::Button::new("Elevate")) .add_enabled(!self.flags.elevated, egui::Button::new("Elevate"))
.on_hover_text("Relaunch ourselves as administrator.") .on_hover_text("Relaunch ourselves as administrator.")
.on_disabled_hover_text("We are already running elevated."); .on_disabled_hover_text("We are already running elevated.");
if button.clicked() { if button.clicked() {
util::win::elevate(util::win::ElevationExitMethod::Gentle( win::elevate(win::ElevationExitMethod::Gentle(&mut self.flags.closing));
&mut self.flags.closing,
));
} }
}); });
}); });
@@ -95,53 +63,17 @@ impl eframe::App for App {
egui::ScrollArea::vertical() egui::ScrollArea::vertical()
.auto_shrink([false, true]) .auto_shrink([false, true])
.show(ui, |ui| match self.stage { .show(ui, |ui| match self.stage {
Stage::Main => { Stage::Main => self.show_main_stage(ctx, ui),
self.show_game(ctx, ui); Stage::Settings => self.show_settings_stage(ctx, ui),
self.show_session(ctx, ui); Stage::About => self.show_about_stage(ctx, ui),
self.show_network(ctx, ui);
}
Stage::Settings => self.show_settings(ctx, ui),
Stage::About => self.show_about(ctx, ui),
}); });
}); });
if tools::check_debug_keycombo_pressed(ctx) { if tools::debug_keycombo_pressed(ctx) || tools::debug_viewport_close_pressed(ctx) {
self.flags.debug = !self.flags.debug; self.flags.debug = !self.flags.debug;
} }
if tools::check_debug_viewport_close_button_pressed(ctx) {
self.flags.debug = false;
}
if self.flags.debug { if self.flags.debug {
let main_rect = ctx.input(|i| { self.show_debug_viewport(ctx);
i.viewport()
.clone()
.outer_rect
.unwrap_or(egui::Rect::EVERYTHING)
});
let position = [main_rect.right(), main_rect.min.y];
ctx.show_viewport_immediate(
egui::ViewportId::from_hash_of("debug_viewport"),
egui::ViewportBuilder::default()
.with_title("GTA Tools Debug")
.with_minimize_button(false)
.with_maximize_button(false)
.with_inner_size(WINDOW_SIZE)
.with_position(position)
.with_icon(tools::load_icon()),
|ctx, _class| {
if tools::check_debug_keycombo_pressed(ctx) {
self.flags.debug = !self.flags.debug;
} }
egui::CentralPanel::default().show(ctx, |ui| {
egui::ScrollArea::both()
.auto_shrink([false, true])
.show(ui, |ui| {
self.show_debug(ctx, ui);
});
});
},
);
}
ctx.request_repaint_after(Duration::from_millis(100));
if self.flags.closing { if self.flags.closing {
ctx.send_viewport_cmd(egui::ViewportCommand::Close); ctx.send_viewport_cmd(egui::ViewportCommand::Close);
} }
@@ -149,42 +81,28 @@ impl eframe::App for App {
} }
impl App { impl App {
fn show_game(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) { fn show_game_section(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) {
tools::header(ui, "Game"); tools::header(ui, "Game");
ui.horizontal(|ui| { ui.horizontal(|ui| {
if ui.button("Launch").clicked() { if ui.button("Launch").clicked() {
features::launch::launch(&self.launch.selected); features::launch::launch(&self.launch.selected);
} }
tools::build_combo_box::<features::launch::Platform>( egui::ComboBox::from_id_salt("Launch")
ui, .selected_text(self.launch.selected.to_string())
&mut self.launch.selected, .width(120.0)
"Launch", .show_ui(ui, |ui| {
); tools::build_menu(ui, &mut self.launch.selected);
});
}); });
let force_close_button = ui.add_sized( let force_close_button = ui.add_sized(
[104.0, 0.0], [104.0, 0.0],
egui::Button::new(&self.force_close.button_text), egui::Button::new(&self.force_close.button_text),
); );
if force_close_button.clicked() && !self.force_close.prompting { self.force_close
self.force_close.prompting(); .prompt(force_close_button.clicked(), &mut self.sysinfo);
self.flags.current_frame = true;
}
if self.force_close.prompting
&& self.force_close.interval.elapsed() <= Duration::from_secs(3)
{
if force_close_button.clicked() && !self.flags.current_frame {
features::force_close::activate(&mut self.sysinfo);
self.force_close = ForceClose::default();
}
} else {
self.force_close = ForceClose::default();
}
if self.flags.current_frame {
self.flags.current_frame = false;
}
} }
fn show_session(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) { fn show_session_section(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) {
tools::header(ui, "Session"); tools::header(ui, "Session");
ui.add_enabled_ui(!self.empty_session.disabled, |ui| { ui.add_enabled_ui(!self.empty_session.disabled, |ui| {
ui.horizontal(|ui| { ui.horizontal(|ui| {
@@ -202,7 +120,7 @@ impl App {
if self.anti_afk.enabled { if self.anti_afk.enabled {
ui.add_space(8.0); ui.add_space(8.0);
ui.add_enabled_ui(false, |ui| { ui.add_enabled_ui(false, |ui| {
ui.label(if util::win::is_window_focused(GTA_WINDOW_TITLE) { ui.label(if win::is_window_focused(WINDOW_TITLE) {
"GTA is focused." "GTA is focused."
} else { } else {
"GTA is not focused!" "GTA is not focused!"
@@ -210,13 +128,12 @@ impl App {
}); });
} }
}); });
if self.anti_afk.enabled && self.anti_afk.interval.elapsed() >= features::anti_afk::INTERVAL if self.anti_afk.can_activate() && self.anti_afk.should_activate() {
{
self.anti_afk.activate(); self.anti_afk.activate();
} }
} }
fn show_network(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) { fn show_network_section(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) {
tools::header(ui, "Network"); tools::header(ui, "Network");
egui::Frame::new() egui::Frame::new()
.outer_margin(egui::vec2(0.0, -2.0)) .outer_margin(egui::vec2(0.0, -2.0))
@@ -231,14 +148,20 @@ impl App {
.add_sized([button_width, 18.0], egui::Button::new("Block")) .add_sized([button_width, 18.0], egui::Button::new("Block"))
.clicked() .clicked()
{ {
features::game_networking::block_all(&mut self.sysinfo); self.game_networking.block_all(&mut self.sysinfo);
} }
if ui if ui
.add_sized([button_width, 18.0], egui::Button::new("Unblock")) .add_sized([button_width, 18.0], egui::Button::new("Unblock"))
.clicked() .clicked()
{ {
features::game_networking::unblock_all(); self.game_networking.unblock_all();
} }
ui.add(
egui::Image::new(egui::include_image!("../../assets/circle.svg"))
.max_size([4.0, 4.0].into())
.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(
@@ -247,7 +170,29 @@ impl App {
}); });
} }
fn show_about(&self, _ctx: &egui::Context, ui: &mut egui::Ui) { fn show_main_stage(&mut self, ctx: &egui::Context, ui: &mut egui::Ui) {
self.show_game_section(ctx, ui);
self.show_session_section(ctx, ui);
self.show_network_section(ctx, ui);
}
fn show_settings_stage(&mut self, ctx: &egui::Context, ui: &mut egui::Ui) {
ui.horizontal(|ui| {
let selection = self.settings.theme;
ui.label("Theme");
egui::ComboBox::from_id_salt("Theme")
.selected_text(self.settings.theme.to_string())
.show_ui(ui, |ui| {
tools::build_menu(ui, &mut self.settings.theme);
});
if selection != self.settings.theme {
catppuccin_egui::set_theme(ctx, self.settings.theme.into());
}
});
ui.checkbox(&mut self.settings.start_elevated, "Always start elevated");
}
fn show_about_stage(&self, _ctx: &egui::Context, ui: &mut egui::Ui) {
ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| { ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| {
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.horizontal(|ui| { ui.horizontal(|ui| {
@@ -282,68 +227,16 @@ impl App {
))); )));
}); });
} }
fn show_settings(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) {
ui.horizontal(|ui| {
ui.label("Theme");
tools::build_combo_box::<settings::Theme>(ui, &mut self.settings.theme, "Theme");
});
ui.checkbox(&mut self.settings.start_elevated, "Always start elevated");
}
fn show_debug(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) {
if ui.button("open storage path").clicked() {
open::that_detached(APP_STORAGE_PATH.as_path()).unwrap();
}
ui.collapsing("anti afk", |ui| {
ui.label(format!(
"timer: {}",
self.anti_afk.interval.elapsed().as_secs()
));
ui.label(format!(
"can activate: {}",
features::anti_afk::can_activate()
));
});
ui.collapsing("sysinfo", |ui| {
if ui.button("refresh all").clicked() {
self.sysinfo.refresh_all();
}
let pid = self
.sysinfo
.processes()
.iter()
.find(|(_, p)| p.name() == ENHANCED || p.name() == LEGACY)
.map_or_else(
|| "no pid found!".to_owned(),
|(pid, _)| pid.as_u32().to_string(),
);
ui.label(format!("gta pid: {pid}"));
});
ui.collapsing("app state", |ui| ui.label(format!("{self:#?}")));
}
fn run_timers(&mut self) {
if self.empty_session.disabled {
self.empty_session.countdown.count();
} else {
self.empty_session.countdown.reset();
}
if self.empty_session.interval.elapsed() >= features::empty_session::INTERVAL {
features::empty_session::deactivate(&mut self.game_handle);
self.empty_session.disabled = false;
}
}
} }
impl Drop for App { impl Drop for App {
fn drop(&mut self) { fn drop(&mut self) {
// save any persistent state to config file // save any persistent state to config file
let persistent_state = PersistentState { PersistentState {
launcher: self.launch.selected, launcher: self.launch.selected,
settings: self.settings.clone(), settings: self.settings.clone(),
}; }
persistent_state.set(); .set();
// make sure we are not suspending game // make sure we are not suspending game
features::empty_session::deactivate(&mut self.game_handle); features::empty_session::deactivate(&mut self.game_handle);
} }
+85
View File
@@ -0,0 +1,85 @@
use crate::{
gui::{
app::{App, WINDOW_SIZE},
tools,
},
util::consts::{
APP_STORAGE_PATH,
game::{EXE_ENHANCED, EXE_LEGACY},
},
};
use eframe::egui;
impl App {
pub fn show_debug_viewport(&mut self, ctx: &egui::Context) {
let main = ctx.input(|i| i.viewport().outer_rect.unwrap_or(egui::Rect::EVERYTHING));
let builder = egui::ViewportBuilder::default()
.with_title("GTA Tools Debug")
.with_minimize_button(false)
.with_maximize_button(false)
.with_inner_size(WINDOW_SIZE)
.with_position([main.right() - 12.0, main.min.y])
.with_icon(tools::load_icon());
ctx.show_viewport_immediate(
egui::ViewportId::from_hash_of("debug_viewport"),
builder,
|ctx, _class| {
if tools::debug_keycombo_pressed(ctx) {
self.flags.debug = !self.flags.debug;
}
egui::CentralPanel::default().show(ctx, |ui| {
egui::ScrollArea::both()
.auto_shrink([false, true])
.show(ui, |ui| {
ui.collapsing("misc", |ui| {
if ui.button("open storage path").clicked() {
open::that_detached(APP_STORAGE_PATH.as_path()).unwrap();
}
ui.checkbox(
&mut self.meta.newer_version_available,
"spoof new version available",
)
.on_hover_text("(this could already be checked if\nthere actually IS a new version)");
ui.scope(|ui| {
use windows::Win32::UI::WindowsAndMessaging::{GetForegroundWindow, GetWindowTextW};
let mut buffer = [0; 512];
let current_title = unsafe {
let hwnd = GetForegroundWindow();
let length = GetWindowTextW(hwnd, &mut buffer);
String::from_utf16_lossy(&buffer[..length as usize])
};
ui.label(format!("focused: \"{current_title}\""));
});
});
ui.collapsing("anti afk", |ui| {
ui.label(format!(
"timer: {}",
self.anti_afk.interval.elapsed().as_secs()
));
ui.label(format!(
"can activate: {}",
self.anti_afk.can_activate()
));
});
ui.collapsing("sysinfo", |ui| {
if ui.button("refresh all").clicked() {
self.sysinfo.refresh_all();
}
let pid = self
.sysinfo
.processes()
.iter()
.find(|(_, p)| p.name() == EXE_ENHANCED || p.name() == EXE_LEGACY)
.map_or_else(
|| "no pid found!".to_owned(),
|(pid, _)| pid.as_u32().to_string(),
);
ui.label(format!("gta pid: {pid}"));
});
ui.collapsing("app state", |ui| ui.label(format!("{self:#?}")));
});
});
},
);
}
}
+1
View File
@@ -1,4 +1,5 @@
mod app; mod app;
mod debug;
pub mod run; pub mod run;
pub mod settings; pub mod settings;
mod tools; mod tools;
+12 -1
View File
@@ -21,26 +21,36 @@ fn panic_hook(panic_info: &std::panic::PanicHookInfo<'_>) {
file.write_all(message.as_bytes()).unwrap(); file.write_all(message.as_bytes()).unwrap();
} }
#[allow(clippy::unnecessary_wraps)]
fn app_creator( fn app_creator(
cc: &eframe::CreationContext<'_>, cc: &eframe::CreationContext<'_>,
) -> Result<Box<dyn eframe::App>, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<Box<dyn eframe::App>, Box<dyn std::error::Error + Send + Sync>> {
// use our own panic hook which logs all panics to a file
std::panic::set_hook(Box::new(panic_hook)); std::panic::set_hook(Box::new(panic_hook));
// initialize App early to modify some things before returning it
let mut app = Box::<App>::default(); let mut app = Box::<App>::default();
// load previously selected launch platform & settings from persistent state
if let Some(persistent_state) = PersistentState::get() { if let Some(persistent_state) = PersistentState::get() {
app.launch.selected = persistent_state.launcher; app.launch.selected = persistent_state.launcher;
app.settings = persistent_state.settings; app.settings = persistent_state.settings;
} }
// check if we're elevated. if not, and the user wants an elevated launch - relaunch elevated
let elevated = util::win::is_elevated(); let elevated = util::win::is_elevated();
if app.settings.start_elevated && !elevated { if app.settings.start_elevated && !elevated {
util::win::elevate(util::win::ElevationExitMethod::Forced); util::win::elevate(util::win::ElevationExitMethod::Forced);
} }
app.flags.elevated = elevated; app.flags.elevated = elevated;
// refresh sysinfo because it initializes with nothing
app.sysinfo.refresh_all();
// enable image loading support in egui
egui_extras::install_image_loaders(&cc.egui_ctx); egui_extras::install_image_loaders(&cc.egui_ctx);
// set our initial theme, from earlier loaded settings
catppuccin_egui::set_theme(&cc.egui_ctx, app.settings.theme.into());
// apply some global styling that we like
cc.egui_ctx.style_mut(|style| { cc.egui_ctx.style_mut(|style| {
style.spacing.item_spacing = egui::vec2(4.0, 4.0); style.spacing.item_spacing = egui::vec2(4.0, 4.0);
style.interaction.selectable_labels = false; style.interaction.selectable_labels = false;
}); });
// load any extra fonts that we need
let mut fonts = egui::FontDefinitions::default(); let mut fonts = egui::FontDefinitions::default();
fonts.font_data.insert( fonts.font_data.insert(
"Ubuntu-Regular".to_owned(), "Ubuntu-Regular".to_owned(),
@@ -51,6 +61,7 @@ fn app_creator(
vec!["Ubuntu-Regular".to_owned()], vec!["Ubuntu-Regular".to_owned()],
); );
cc.egui_ctx.set_fonts(fonts); cc.egui_ctx.set_fonts(fonts);
// finally return the App
Ok(app) Ok(app)
} }
+6 -22
View File
@@ -1,13 +1,15 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Display; use strum::{Display, EnumIter};
use strum::EnumIter;
#[allow(clippy::enum_variant_names)] #[derive(Clone, Copy, Debug, Display, PartialEq, Eq, Serialize, Deserialize, EnumIter)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, EnumIter)]
pub enum Theme { pub enum Theme {
#[strum(to_string = "Catppuccin Latte")]
CatppuccinLatte, CatppuccinLatte,
#[strum(to_string = "Catppuccin Frappe")]
CatppuccinFrappe, CatppuccinFrappe,
#[strum(to_string = "Catppuccin Macchiato")]
CatppuccinMacchiato, CatppuccinMacchiato,
#[strum(to_string = "Catppuccin Mocha")]
CatppuccinMocha, CatppuccinMocha,
} }
@@ -22,24 +24,6 @@ impl From<Theme> for catppuccin_egui::Theme {
} }
} }
impl Display for Theme {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let x = match self {
Self::CatppuccinLatte => "Catppuccin Latte",
Self::CatppuccinFrappe => "Catppuccin Frappe",
Self::CatppuccinMacchiato => "Catppuccin Macchiato",
Self::CatppuccinMocha => "Catppuccin Mocha",
};
write!(f, "{x}")
}
}
impl Theme {
pub fn to_catppuccin(self) -> catppuccin_egui::Theme {
self.into()
}
}
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Settings { pub struct Settings {
pub theme: Theme, pub theme: Theme,
+5 -9
View File
@@ -21,24 +21,20 @@ pub fn header(ui: &mut egui::Ui, text: &str) {
}); });
} }
pub fn build_combo_box<E>(ui: &mut egui::Ui, current_value: &mut E, label: impl std::hash::Hash) pub fn build_menu<E>(ui: &mut egui::Ui, current_value: &mut E)
where where
E: strum::IntoEnumIterator + std::fmt::Display + std::cmp::PartialEq + Copy, E: strum::IntoEnumIterator + std::fmt::Display + std::cmp::PartialEq + Copy,
{ {
egui::ComboBox::from_id_salt(label) E::iter().for_each(|variant| {
.selected_text(current_value.to_string()) ui.selectable_value(current_value, variant, variant.to_string());
.show_ui(ui, |ui| {
E::iter().for_each(|v| {
ui.selectable_value(current_value, v, v.to_string());
});
}); });
} }
pub fn check_debug_keycombo_pressed(ctx: &egui::Context) -> bool { pub fn debug_keycombo_pressed(ctx: &egui::Context) -> bool {
ctx.input(|i| i.modifiers.all() && i.key_pressed(egui::Key::D)) ctx.input(|i| i.modifiers.all() && i.key_pressed(egui::Key::D))
} }
pub fn check_debug_viewport_close_button_pressed(ctx: &egui::Context) -> bool { pub fn debug_viewport_close_pressed(ctx: &egui::Context) -> bool {
ctx.input(|i| { ctx.input(|i| {
i.raw i.raw
.viewports .viewports
+13 -5
View File
@@ -1,9 +1,17 @@
use std::{path::PathBuf, sync::LazyLock}; use std::{path::PathBuf, sync::LazyLock};
pub const ENHANCED: &str = "GTA5_Enhanced.exe";
pub const LEGACY: &str = "GTA5.exe";
pub const GTA_WINDOW_TITLE: &str = "Grand Theft Auto V";
pub static APP_STORAGE_PATH: LazyLock<PathBuf> = pub static APP_STORAGE_PATH: LazyLock<PathBuf> =
LazyLock::new(|| dirs::config_local_dir().unwrap().join("GTA Tools")); LazyLock::new(|| dirs::config_local_dir().unwrap().join("GTA Tools"));
pub mod game {
pub const EXE_ENHANCED: &str = "GTA5_Enhanced.exe";
pub const EXE_LEGACY: &str = "GTA5.exe";
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);
}
+4 -4
View File
@@ -2,11 +2,11 @@ use std::time::{Duration, Instant};
#[derive(Debug)] #[derive(Debug)]
pub struct Countdown { pub struct Countdown {
pub i: u64, i: u64,
pub i_original: u64, i_original: u64,
pub i_string: String, pub i_string: String,
pub interval: Instant, interval: Instant,
pub first_count: bool, first_count: bool,
} }
impl Countdown { impl Countdown {
+1 -1
View File
@@ -1,4 +1,4 @@
pub mod codeberg; mod codeberg;
pub mod consts; pub mod consts;
pub mod countdown; pub mod countdown;
pub mod meta; pub mod meta;
+3 -4
View File
@@ -4,7 +4,7 @@ use windows::{
Security::{GetTokenInformation, TOKEN_ELEVATION, TOKEN_QUERY, TokenElevation}, Security::{GetTokenInformation, TOKEN_ELEVATION, TOKEN_QUERY, TokenElevation},
System::Threading::{GetCurrentProcess, OpenProcessToken}, System::Threading::{GetCurrentProcess, OpenProcessToken},
UI::{ UI::{
Input::KeyboardAndMouse::GetAsyncKeyState, Input::KeyboardAndMouse::{GetAsyncKeyState, VIRTUAL_KEY},
Shell::ShellExecuteW, Shell::ShellExecuteW,
WindowsAndMessaging::{ WindowsAndMessaging::{
CURSOR_SHOWING, CURSORINFO, GetCursorInfo, GetForegroundWindow, GetWindowTextW, CURSOR_SHOWING, CURSORINFO, GetCursorInfo, GetForegroundWindow, GetWindowTextW,
@@ -31,7 +31,6 @@ pub fn is_cursor_visible() -> bool {
ci.flags == CURSOR_SHOWING ci.flags == CURSOR_SHOWING
} }
#[allow(clippy::cast_sign_loss)]
pub fn is_window_focused(target_title: &str) -> bool { pub fn is_window_focused(target_title: &str) -> bool {
let mut buffer = [0; 512]; let mut buffer = [0; 512];
unsafe { unsafe {
@@ -42,9 +41,9 @@ pub fn is_window_focused(target_title: &str) -> bool {
} }
} }
pub fn is_any_key_pressed(keys: &[u8]) -> bool { pub fn is_any_key_pressed(keys: &[VIRTUAL_KEY]) -> bool {
keys.iter() keys.iter()
.any(|&key| unsafe { (GetAsyncKeyState(i32::from(key)) & i16::MIN) != 0 }) .any(|&key| unsafe { (GetAsyncKeyState(i32::from(key.0)) & i16::MIN) != 0 })
} }
pub fn elevate(closing: ElevationExitMethod) { pub fn elevate(closing: ElevationExitMethod) {