12 Commits
Author SHA1 Message Date
futile 4ce7f3459d 0.4.1 2025-04-17 02:33:27 +01:00
futile c8cbb51f43 thgank you clippy that's very smar thtat's very clever 2025-04-17 02:27:10 +01:00
futile 9c53239276 remove an i32 cast from is_any_key_pressed 2025-04-17 02:25:07 +01:00
futile af5ea259e9 implicit type signature on buffer var in is_window_focused 2025-04-17 02:12:34 +01:00
futile 162350699a change flow of util::win::is_elevated 2025-04-17 02:01:31 +01:00
futile 2977398437 slightly reduce unsafe scopes 2025-04-17 01:48:45 +01:00
futile 36dce5aa1e reduce ai idiocy 2025-04-17 01:39:01 +01:00
futile 6935b852ce clippy 2025-04-17 01:31:49 +01:00
futile 1c048e6aff revert 33fbbc7f 2025-04-17 00:50:09 +01:00
futile 84a659a2b1 add button to debug to open app storage path 2025-04-17 00:49:20 +01:00
futile db4ca22f95 log panics to a file 2025-04-17 00:44:06 +01:00
futile 284c4bb46d add a mouse cursor check to anti afk activation 2025-04-16 23:59:50 +01:00
10 changed files with 130 additions and 45 deletions
Generated
+46 -1
View File
@@ -156,6 +156,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]] [[package]]
name = "android_system_properties" name = "android_system_properties"
version = "0.1.5" version = "0.1.5"
@@ -596,6 +602,20 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "chrono"
version = "0.4.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-link",
]
[[package]] [[package]]
name = "clipboard-win" name = "clipboard-win"
version = "5.4.0" version = "5.4.0"
@@ -1366,9 +1386,10 @@ dependencies = [
[[package]] [[package]]
name = "gta-tools" name = "gta-tools"
version = "0.4.0" version = "0.4.1"
dependencies = [ dependencies = [
"catppuccin-egui", "catppuccin-egui",
"chrono",
"dirs", "dirs",
"eframe", "eframe",
"egui_extras", "egui_extras",
@@ -1426,6 +1447,30 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "iana-time-zone"
version = "0.1.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"log",
"wasm-bindgen",
"windows-core 0.61.0",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "icu_collections" name = "icu_collections"
version = "1.5.0" version = "1.5.0"
+2 -1
View File
@@ -1,12 +1,13 @@
[package] [package]
name = "gta-tools" name = "gta-tools"
version = "0.4.0" version = "0.4.1"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
catppuccin-egui = { version = "5.5.0", default-features = false, features = [ catppuccin-egui = { version = "5.5.0", default-features = false, features = [
"egui31", "egui31",
] } ] }
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"] }
+6 -3
View File
@@ -26,15 +26,18 @@ impl Default for AntiAfk {
impl AntiAfk { impl AntiAfk {
pub fn activate(&mut self) { pub fn activate(&mut self) {
if util::win::is_window_focused(GTA_WINDOW_TITLE) if can_activate() {
&& !util::win::is_any_key_pressed(&PRESS_KEYS)
{
send(&PRESS_KEYS); send(&PRESS_KEYS);
} }
self.interval = Instant::now(); self.interval = Instant::now();
} }
} }
pub fn can_activate() -> bool {
use util::win::{is_any_key_pressed, is_cursor_visible, is_window_focused};
is_window_focused(GTA_WINDOW_TITLE) && !is_any_key_pressed(&PRESS_KEYS) && !is_cursor_visible()
}
pub fn send(vk_codes: &[u8]) { pub fn send(vk_codes: &[u8]) {
vk_codes.iter().for_each(|vk_code: &u8| unsafe { vk_codes.iter().for_each(|vk_code: &u8| unsafe {
keybd_event( keybd_event(
+1 -1
View File
@@ -41,7 +41,7 @@ fn get_gta_pid(sysinfo: &mut System) -> u32 {
.find(|(_, p)| p.name() == ENHANCED || p.name() == LEGACY) .find(|(_, p)| p.name() == ENHANCED || p.name() == LEGACY)
{ {
return pid.as_u32(); return pid.as_u32();
}; }
u32::MAX u32::MAX
} }
+35 -10
View File
@@ -9,11 +9,15 @@ use crate::{
gui::{persistent_state::PersistentState, settings::Settings}, gui::{persistent_state::PersistentState, settings::Settings},
util::{ util::{
self, self,
consts::{ENHANCED, GTA_WINDOW_TITLE, LEGACY}, consts::{APP_STORAGE_PATH, ENHANCED, GTA_WINDOW_TITLE, LEGACY},
}, },
}; };
use eframe::egui; use eframe::egui;
use std::time::{Duration, Instant}; use std::{
fs,
io::Write,
time::{Duration, Instant},
};
const WINDOW_SIZE: [f32; 2] = [240.0, 245.0]; const WINDOW_SIZE: [f32; 2] = [240.0, 245.0];
@@ -150,7 +154,7 @@ impl App {
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);
}; }
build_combo_box::<features::launch::Platform>(ui, &mut self.launch.selected, "Launch"); build_combo_box::<features::launch::Platform>(ui, &mut self.launch.selected, "Launch");
}); });
let force_close_button = ui.add_sized( let force_close_button = ui.add_sized(
@@ -160,7 +164,7 @@ impl App {
if force_close_button.clicked() && !self.force_close.prompting { if force_close_button.clicked() && !self.force_close.prompting {
self.force_close.prompting(); self.force_close.prompting();
self.flags.current_frame = true; self.flags.current_frame = true;
}; }
if self.force_close.prompting if self.force_close.prompting
&& self.force_close.interval.elapsed() <= Duration::from_secs(3) && self.force_close.interval.elapsed() <= Duration::from_secs(3)
{ {
@@ -224,13 +228,13 @@ impl App {
.clicked() .clicked()
{ {
features::game_networking::block_all(&mut self.sysinfo); features::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(); features::game_networking::unblock_all();
}; }
}); });
}); });
response.response.on_disabled_hover_text( response.response.on_disabled_hover_text(
@@ -271,11 +275,18 @@ impl App {
} }
fn show_debug(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) { fn show_debug(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) {
ui.collapsing("times", |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!( ui.label(format!(
"anti afk timer: {}", "timer: {}",
self.anti_afk.interval.elapsed().as_secs() self.anti_afk.interval.elapsed().as_secs()
)) ));
ui.label(format!(
"can activate: {}",
features::anti_afk::can_activate()
));
}); });
ui.collapsing("sysinfo", |ui| { ui.collapsing("sysinfo", |ui| {
if ui.button("refresh all").clicked() { if ui.button("refresh all").clicked() {
@@ -369,15 +380,29 @@ fn load_icon() -> egui::IconData {
} }
} }
fn panic_hook(panic_info: &std::panic::PanicHookInfo<'_>) {
let log_path = APP_STORAGE_PATH.join("panic.log");
let mut file = fs::File::options()
.create(true)
.append(true)
.open(log_path)
.unwrap();
let timestamp = chrono::Local::now().to_rfc3339();
let backtrace = std::backtrace::Backtrace::force_capture();
let message = format!("[{timestamp}]\n{panic_info}\n\n{backtrace}\n\n");
file.write_all(message.as_bytes()).unwrap();
}
#[allow(clippy::unnecessary_wraps)] #[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>> {
std::panic::set_hook(Box::new(panic_hook));
let mut app = Box::<App>::default(); let mut app = Box::<App>::default();
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;
}; }
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);
+2 -7
View File
@@ -1,4 +1,4 @@
use crate::{features::launch::Platform, gui::settings::Settings}; use crate::{features::launch::Platform, gui::settings::Settings, util::consts::APP_STORAGE_PATH};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
fs::{self, File}, fs::{self, File},
@@ -7,12 +7,7 @@ use std::{
sync::LazyLock, sync::LazyLock,
}; };
static CONFIG_PATH: LazyLock<PathBuf> = LazyLock::new(|| { static CONFIG_PATH: LazyLock<PathBuf> = LazyLock::new(|| APP_STORAGE_PATH.join("config.json"));
dirs::config_local_dir()
.unwrap()
.join("GTA Tools")
.join("config.json")
});
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct PersistentState { pub struct PersistentState {
+1 -1
View File
@@ -1,4 +1,4 @@
#![windows_subsystem = "windows"] #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
mod features; mod features;
mod gui; mod gui;
+5
View File
@@ -1,4 +1,9 @@
use std::{path::PathBuf, sync::LazyLock};
pub const ENHANCED: &str = "GTA5_Enhanced.exe"; pub const ENHANCED: &str = "GTA5_Enhanced.exe";
pub const LEGACY: &str = "GTA5.exe"; pub const LEGACY: &str = "GTA5.exe";
pub const GTA_WINDOW_TITLE: &str = "Grand Theft Auto V"; pub const GTA_WINDOW_TITLE: &str = "Grand Theft Auto V";
pub static APP_STORAGE_PATH: LazyLock<PathBuf> =
LazyLock::new(|| dirs::config_local_dir().unwrap().join("GTA Tools"));
+1 -1
View File
@@ -36,6 +36,6 @@ impl Countdown {
self.i_string = self.i.to_string(); self.i_string = self.i.to_string();
if self.i == 0 { if self.i == 0 {
self.reset(); self.reset();
}; }
} }
} }
+31 -20
View File
@@ -6,7 +6,10 @@ use windows::{
UI::{ UI::{
Input::KeyboardAndMouse::GetAsyncKeyState, Input::KeyboardAndMouse::GetAsyncKeyState,
Shell::ShellExecuteW, Shell::ShellExecuteW,
WindowsAndMessaging::{GetForegroundWindow, GetWindowTextW, SW_NORMAL}, WindowsAndMessaging::{
CURSOR_SHOWING, CURSORINFO, GetCursorInfo, GetForegroundWindow, GetWindowTextW,
SW_NORMAL,
},
}, },
}, },
core::{HSTRING, PCWSTR}, core::{HSTRING, PCWSTR},
@@ -17,11 +20,22 @@ pub enum ElevationExitMethod<'a> {
Forced, Forced,
} }
pub fn is_cursor_visible() -> bool {
let mut ci = CURSORINFO {
cbSize: u32::try_from(std::mem::size_of::<CURSORINFO>()).unwrap(),
..Default::default()
};
unsafe {
GetCursorInfo(&mut ci).unwrap();
}
ci.flags == CURSOR_SHOWING
}
#[allow(clippy::cast_sign_loss)] #[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];
unsafe { unsafe {
let hwnd = GetForegroundWindow(); let hwnd = GetForegroundWindow();
let mut buffer: [u16; 512] = [0; 512];
let length = GetWindowTextW(hwnd, &mut buffer); let length = GetWindowTextW(hwnd, &mut buffer);
let current_title = String::from_utf16_lossy(&buffer[..length as usize]); let current_title = String::from_utf16_lossy(&buffer[..length as usize]);
current_title == target_title current_title == target_title
@@ -30,7 +44,7 @@ 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: &[u8]) -> bool {
keys.iter() keys.iter()
.any(|&key| unsafe { (i32::from(GetAsyncKeyState(i32::from(key))) & 0x8000) != 0 }) .any(|&key| unsafe { (GetAsyncKeyState(i32::from(key)) & i16::MIN) != 0 })
} }
pub fn elevate(closing: ElevationExitMethod) { pub fn elevate(closing: ElevationExitMethod) {
@@ -52,24 +66,21 @@ pub fn elevate(closing: ElevationExitMethod) {
} }
pub fn is_elevated() -> bool { pub fn is_elevated() -> bool {
let mut token: HANDLE = HANDLE::default();
unsafe { unsafe {
let mut token: HANDLE = HANDLE::default(); if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token).is_err() {
if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token).is_ok() { return false;
let mut elevation = TOKEN_ELEVATION::default();
let size = u32::try_from(std::mem::size_of::<TOKEN_ELEVATION>()).unwrap();
let mut ret_size = size;
let result = GetTokenInformation(
token,
TokenElevation,
Some((&raw mut elevation).cast()),
size,
&mut ret_size,
);
CloseHandle(token).unwrap();
if result.is_ok() && elevation.TokenIsElevated != 0 {
return true;
}
} }
false let mut elevation = TOKEN_ELEVATION::default();
let mut size = u32::try_from(std::mem::size_of::<TOKEN_ELEVATION>()).unwrap();
let result = GetTokenInformation(
token,
TokenElevation,
Some((&raw mut elevation).cast()),
size,
&mut size,
);
CloseHandle(token).unwrap();
result.is_ok() && elevation.TokenIsElevated != 0
} }
} }