Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ce7f3459d | ||
|
|
c8cbb51f43 | ||
|
|
9c53239276 | ||
|
|
af5ea259e9 | ||
|
|
162350699a | ||
|
|
2977398437 | ||
|
|
36dce5aa1e | ||
|
|
6935b852ce | ||
|
|
1c048e6aff | ||
|
|
84a659a2b1 | ||
|
|
db4ca22f95 | ||
|
|
284c4bb46d | ||
|
|
5fe697223c | ||
|
|
33fbbc7fc0 | ||
|
|
5bcdccdfea | ||
|
|
31ac09cd25 | ||
|
|
1283f34174 | ||
|
|
072c8ae406 | ||
|
|
e283393889 | ||
|
|
cf5c37c7dc | ||
|
|
c6cd0cf2f9 | ||
|
|
d182ca5318 | ||
|
|
42ee688639 | ||
|
|
ba49ed0e67 | ||
|
|
f9f326904e | ||
|
|
c3a1e864a7 | ||
|
|
7f795ba52e | ||
|
|
62efe3f411 | ||
|
|
4bb0f151a9 | ||
|
|
1aebc6b17c | ||
|
|
861ebe97d6 | ||
|
|
53b20a437a | ||
|
|
03ca861a09 | ||
|
|
a4ebfc515c | ||
|
|
52ffb34d83 | ||
|
|
6248e8379f | ||
|
|
99bc15b5ed | ||
|
|
e84d1347fd |
Generated
+71
-3
@@ -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.2.1"
|
version = "0.4.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"catppuccin-egui",
|
"catppuccin-egui",
|
||||||
|
"chrono",
|
||||||
"dirs",
|
"dirs",
|
||||||
"eframe",
|
"eframe",
|
||||||
"egui_extras",
|
"egui_extras",
|
||||||
@@ -1377,6 +1398,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"static_vcruntime",
|
"static_vcruntime",
|
||||||
|
"strum 0.27.1",
|
||||||
"sysinfo",
|
"sysinfo",
|
||||||
"windows 0.61.1",
|
"windows 0.61.1",
|
||||||
"winreg",
|
"winreg",
|
||||||
@@ -1425,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"
|
||||||
@@ -1845,7 +1891,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"spirv",
|
"spirv",
|
||||||
"strum",
|
"strum 0.26.3",
|
||||||
"termcolor",
|
"termcolor",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
@@ -2867,7 +2913,16 @@ version = "0.26.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
|
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"strum_macros",
|
"strum_macros 0.26.4",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum"
|
||||||
|
version = "0.27.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32"
|
||||||
|
dependencies = [
|
||||||
|
"strum_macros 0.27.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2883,6 +2938,19 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum_macros"
|
||||||
|
version = "0.27.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rustversion",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.100"
|
version = "2.0.100"
|
||||||
|
|||||||
+3
-1
@@ -1,12 +1,13 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "gta-tools"
|
name = "gta-tools"
|
||||||
version = "0.2.1"
|
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"] }
|
||||||
@@ -14,6 +15,7 @@ image = { version = "0.25.6", default-features = false, features = ["png"] }
|
|||||||
open = "5.3.2"
|
open = "5.3.2"
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
serde_json = "1.0.140"
|
serde_json = "1.0.140"
|
||||||
|
strum = { version = "0.27.1", features = ["derive"] }
|
||||||
sysinfo = "0.34.2"
|
sysinfo = "0.34.2"
|
||||||
windows = { version = "0.61.1", features = [
|
windows = { version = "0.61.1", features = [
|
||||||
"Win32_UI_Input_KeyboardAndMouse",
|
"Win32_UI_Input_KeyboardAndMouse",
|
||||||
|
|||||||
Binary file not shown.
@@ -5,5 +5,5 @@ build:
|
|||||||
upx .\target\release\gta-tools.exe --best
|
upx .\target\release\gta-tools.exe --best
|
||||||
|
|
||||||
install: build
|
install: build
|
||||||
cp .\target\release\gta-tools.exe ~\.cargo\bin\
|
cp .\target\release\gta-tools.exe ~\.cargo\bin
|
||||||
cp .\target\release\gta-tools.exe ~\Documents\
|
cp .\target\release\gta-tools.exe ~\Documents
|
||||||
|
|||||||
+26
-13
@@ -1,10 +1,13 @@
|
|||||||
|
#![allow(clippy::cast_possible_truncation)]
|
||||||
|
|
||||||
|
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, keybd_event,
|
KEYBD_EVENT_FLAGS, MAP_VIRTUAL_KEY_TYPE, MapVirtualKeyW, VK_NUMPAD4, VK_NUMPAD6, keybd_event,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const INTERVAL: Duration = Duration::from_secs(60);
|
pub const INTERVAL: Duration = Duration::from_secs(60);
|
||||||
const VK_SHIFT: u8 = 16;
|
const PRESS_KEYS: [u8; 2] = [VK_NUMPAD4.0 as u8, VK_NUMPAD6.0 as u8];
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct AntiAfk {
|
pub struct AntiAfk {
|
||||||
@@ -21,23 +24,33 @@ impl Default for AntiAfk {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send(vk_code: u8) {
|
impl AntiAfk {
|
||||||
unsafe {
|
pub fn activate(&mut self) {
|
||||||
|
if can_activate() {
|
||||||
|
send(&PRESS_KEYS);
|
||||||
|
}
|
||||||
|
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]) {
|
||||||
|
vk_codes.iter().for_each(|vk_code: &u8| unsafe {
|
||||||
keybd_event(
|
keybd_event(
|
||||||
vk_code,
|
*vk_code,
|
||||||
u8::try_from(MapVirtualKeyW(u32::from(vk_code), MAP_VIRTUAL_KEY_TYPE(0))).unwrap(),
|
u8::try_from(MapVirtualKeyW(u32::from(*vk_code), MAP_VIRTUAL_KEY_TYPE(0))).unwrap(),
|
||||||
KEYBD_EVENT_FLAGS(0),
|
KEYBD_EVENT_FLAGS(0),
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
keybd_event(
|
keybd_event(
|
||||||
vk_code,
|
*vk_code,
|
||||||
u8::try_from(MapVirtualKeyW(u32::from(vk_code), MAP_VIRTUAL_KEY_TYPE(0))).unwrap(),
|
u8::try_from(MapVirtualKeyW(u32::from(*vk_code), MAP_VIRTUAL_KEY_TYPE(0))).unwrap(),
|
||||||
KEYBD_EVENT_FLAGS(2),
|
KEYBD_EVENT_FLAGS(2),
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
}
|
|
||||||
|
|
||||||
pub fn activate() {
|
|
||||||
send(VK_SHIFT);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use windows::Win32::{
|
|||||||
System::Threading::{OpenProcess, PROCESS_SUSPEND_RESUME},
|
System::Threading::{OpenProcess, PROCESS_SUSPEND_RESUME},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const INTERVAL: Duration = Duration::from_secs(8);
|
pub const INTERVAL: Duration = Duration::from_secs(10);
|
||||||
const ENHANCED: &str = "GTA5_Enhanced.exe";
|
const ENHANCED: &str = "GTA5_Enhanced.exe";
|
||||||
const LEGACY: &str = "GTA5.exe";
|
const LEGACY: &str = "GTA5.exe";
|
||||||
|
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +58,9 @@ pub fn activate(app: &mut App) {
|
|||||||
|
|
||||||
pub fn deactivate(app: &mut App) {
|
pub fn deactivate(app: &mut App) {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
if !app.game_handle.is_invalid() {
|
||||||
let _ = NtResumeProcess(app.game_handle);
|
let _ = NtResumeProcess(app.game_handle);
|
||||||
let _ = CloseHandle(app.game_handle);
|
let _ = CloseHandle(app.game_handle);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{fmt::Display, path::PathBuf, process::Command};
|
use std::{fmt::Display, path::PathBuf, process::Command};
|
||||||
|
use strum::EnumIter;
|
||||||
use winreg::{RegKey, enums::HKEY_LOCAL_MACHINE};
|
use winreg::{RegKey, enums::HKEY_LOCAL_MACHINE};
|
||||||
|
|
||||||
#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Serialize, Deserialize, EnumIter)]
|
||||||
pub enum Platform {
|
pub enum Platform {
|
||||||
#[default]
|
#[default]
|
||||||
Steam,
|
Steam,
|
||||||
|
|||||||
-373
@@ -1,373 +0,0 @@
|
|||||||
use crate::{
|
|
||||||
features::{
|
|
||||||
self,
|
|
||||||
anti_afk::AntiAfk,
|
|
||||||
empty_session::EmptySession,
|
|
||||||
force_close::ForceClose,
|
|
||||||
launch::{Launch, Platform},
|
|
||||||
},
|
|
||||||
util::{
|
|
||||||
consts::{ENHANCED, LEGACY},
|
|
||||||
elevation,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use eframe::egui;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::{
|
|
||||||
fs::{self, File},
|
|
||||||
io::Write,
|
|
||||||
path::PathBuf,
|
|
||||||
sync::LazyLock,
|
|
||||||
time::{Duration, Instant},
|
|
||||||
};
|
|
||||||
use sysinfo::System;
|
|
||||||
use windows::Win32::Foundation::HANDLE;
|
|
||||||
|
|
||||||
const THEME: catppuccin_egui::Theme = catppuccin_egui::MOCHA;
|
|
||||||
static CONFIG_PATH: LazyLock<PathBuf> = LazyLock::new(|| {
|
|
||||||
let mut config_path = dirs::config_local_dir().unwrap();
|
|
||||||
config_path.push("GTA Tools");
|
|
||||||
config_path.push("config.json");
|
|
||||||
config_path
|
|
||||||
});
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct PersistentState {
|
|
||||||
launcher: Platform,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, PartialEq, Eq)]
|
|
||||||
pub enum Stage {
|
|
||||||
#[default]
|
|
||||||
Main,
|
|
||||||
About,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::struct_excessive_bools)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct App {
|
|
||||||
stage: Stage,
|
|
||||||
closing: bool,
|
|
||||||
debug: bool,
|
|
||||||
initialized: bool,
|
|
||||||
elevated: bool,
|
|
||||||
current_frame: bool,
|
|
||||||
pub sysinfo: System,
|
|
||||||
pub game_handle: HANDLE,
|
|
||||||
launch: Launch,
|
|
||||||
force_close: ForceClose,
|
|
||||||
empty_session: EmptySession,
|
|
||||||
anti_afk: AntiAfk,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for App {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
stage: Stage::default(),
|
|
||||||
closing: false,
|
|
||||||
initialized: false,
|
|
||||||
debug: false,
|
|
||||||
elevated: elevation::is_elevated(),
|
|
||||||
current_frame: false,
|
|
||||||
sysinfo: System::new_all(),
|
|
||||||
game_handle: HANDLE::default(),
|
|
||||||
launch: Launch::default(),
|
|
||||||
force_close: ForceClose::default(),
|
|
||||||
empty_session: EmptySession::default(),
|
|
||||||
anti_afk: AntiAfk::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl eframe::App for App {
|
|
||||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
|
||||||
if !self.initialized {
|
|
||||||
catppuccin_egui::set_theme(ctx, THEME);
|
|
||||||
egui_extras::install_image_loaders(ctx);
|
|
||||||
if let Ok(config) = fs::read_to_string(CONFIG_PATH.as_path()) {
|
|
||||||
if let Ok(persistent_state) = serde_json::from_str::<PersistentState>(&config) {
|
|
||||||
self.launch.selected = persistent_state.launcher;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.initialized = true;
|
|
||||||
}
|
|
||||||
self.run_timers();
|
|
||||||
egui::TopBottomPanel::bottom("bottom_panel").show(ctx, |ui| {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.selectable_value(&mut self.stage, Stage::Main, "Main");
|
|
||||||
ui.selectable_value(&mut self.stage, Stage::About, "About");
|
|
||||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
|
||||||
let button = ui
|
|
||||||
.add_enabled(!self.elevated, egui::Button::new("Elevate"))
|
|
||||||
.on_hover_text("Relaunch ourselves as administrator.")
|
|
||||||
.on_disabled_hover_text("We are already running elevated.");
|
|
||||||
if button.clicked() {
|
|
||||||
elevation::elevate(&mut self.closing);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
|
||||||
egui::ScrollArea::both()
|
|
||||||
.auto_shrink([false, false])
|
|
||||||
.show(ui, |ui| match self.stage {
|
|
||||||
Stage::Main => {
|
|
||||||
self.header(ui, "Game");
|
|
||||||
self.show_game(ctx, ui);
|
|
||||||
self.header(ui, "Session");
|
|
||||||
self.show_session(ctx, ui);
|
|
||||||
self.header(ui, "Network");
|
|
||||||
self.show_network(ctx, ui);
|
|
||||||
}
|
|
||||||
Stage::About => {
|
|
||||||
self.show_about(ctx, ui);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
if self.check_debug_keycombo_pressed(ctx) {
|
|
||||||
self.debug = !self.debug;
|
|
||||||
}
|
|
||||||
if self.check_debug_viewport_close_button_pressed(ctx) {
|
|
||||||
self.debug = false;
|
|
||||||
}
|
|
||||||
if self.debug {
|
|
||||||
let main_rect = ctx.input(|i| i.viewport().clone().outer_rect.unwrap());
|
|
||||||
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([256.0, 232.0])
|
|
||||||
.with_position(position)
|
|
||||||
.with_icon(load_icon()),
|
|
||||||
|ctx, _class| {
|
|
||||||
if self.check_debug_keycombo_pressed(ctx) {
|
|
||||||
self.debug = !self.debug;
|
|
||||||
}
|
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
|
||||||
egui::ScrollArea::both()
|
|
||||||
.auto_shrink([false, false])
|
|
||||||
.show(ui, |ui| {
|
|
||||||
self.show_debug(ctx, ui);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
ctx.request_repaint_after(Duration::from_millis(100));
|
|
||||||
if self.closing {
|
|
||||||
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl App {
|
|
||||||
fn check_debug_keycombo_pressed(&self, ctx: &egui::Context) -> bool {
|
|
||||||
ctx.input(|i| i.modifiers.all() && i.key_pressed(egui::Key::D))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_debug_viewport_close_button_pressed(&self, ctx: &egui::Context) -> bool {
|
|
||||||
ctx.input(|i| {
|
|
||||||
i.raw
|
|
||||||
.viewports
|
|
||||||
.get(&egui::ViewportId::from_hash_of("debug_viewport"))
|
|
||||||
.filter(|vp| vp.close_requested())
|
|
||||||
.is_some()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::unused_self)]
|
|
||||||
fn header(&self, ui: &mut egui::Ui, text: &str) {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.label(text);
|
|
||||||
ui.add(egui::Separator::default().horizontal());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn show_game(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
if ui.button("Launch").clicked() {
|
|
||||||
features::launch::launch(&self.launch.selected);
|
|
||||||
};
|
|
||||||
egui::ComboBox::from_label("")
|
|
||||||
.selected_text(self.launch.selected.to_string())
|
|
||||||
.width(120.0)
|
|
||||||
.show_ui(ui, |ui| {
|
|
||||||
ui.selectable_value(
|
|
||||||
&mut self.launch.selected,
|
|
||||||
Platform::Steam,
|
|
||||||
Platform::Steam.to_string(),
|
|
||||||
);
|
|
||||||
ui.selectable_value(
|
|
||||||
&mut self.launch.selected,
|
|
||||||
Platform::Rockstar,
|
|
||||||
Platform::Rockstar.to_string(),
|
|
||||||
);
|
|
||||||
ui.selectable_value(
|
|
||||||
&mut self.launch.selected,
|
|
||||||
Platform::Epic,
|
|
||||||
Platform::Epic.to_string(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
let force_close_button = ui.button(&self.force_close.button_text);
|
|
||||||
if force_close_button.clicked() && !self.force_close.prompting {
|
|
||||||
self.force_close.prompting();
|
|
||||||
self.current_frame = true;
|
|
||||||
};
|
|
||||||
if self.force_close.prompting
|
|
||||||
&& self.force_close.interval.elapsed() <= Duration::from_secs(3)
|
|
||||||
{
|
|
||||||
if force_close_button.clicked() && !self.current_frame {
|
|
||||||
features::force_close::activate(&mut self.sysinfo);
|
|
||||||
self.force_close = ForceClose::default();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.force_close = ForceClose::default();
|
|
||||||
}
|
|
||||||
if self.current_frame {
|
|
||||||
self.current_frame = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn show_session(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) {
|
|
||||||
ui.add_enabled_ui(!self.empty_session.disabled, |ui| {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
if ui.button("Empty current session").clicked() {
|
|
||||||
self.empty_session.interval = Instant::now();
|
|
||||||
self.empty_session.disabled = true;
|
|
||||||
features::empty_session::activate(self);
|
|
||||||
}
|
|
||||||
ui.label(&self.empty_session.countdown.i_string);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
ui.checkbox(&mut self.anti_afk.enabled, "Anti AFK")
|
|
||||||
.on_hover_text("You should be tabbed in\nwhile this is enabled.");
|
|
||||||
if self.anti_afk.enabled && self.anti_afk.interval.elapsed() >= features::anti_afk::INTERVAL
|
|
||||||
{
|
|
||||||
features::anti_afk::activate();
|
|
||||||
self.anti_afk.interval = Instant::now();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn show_network(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) {
|
|
||||||
egui::Frame::new()
|
|
||||||
.inner_margin(egui::vec2(4.0, 4.0))
|
|
||||||
.stroke(egui::Stroke::new(1.0, THEME.overlay1))
|
|
||||||
.show(ui, |ui| {
|
|
||||||
let response = ui.add_enabled_ui(self.elevated, |ui| {
|
|
||||||
ui.label("Game's network access");
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
if ui.button("Block").clicked() {
|
|
||||||
features::game_networking::block_all(&mut self.sysinfo);
|
|
||||||
};
|
|
||||||
if ui.button("Unblock").clicked() {
|
|
||||||
features::game_networking::unblock_all();
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
response
|
|
||||||
.response
|
|
||||||
.on_disabled_hover_text("This requires administrator.\nUse the Elevate button.")
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::unused_self)]
|
|
||||||
fn show_about(&self, _ctx: &egui::Context, ui: &mut egui::Ui) {
|
|
||||||
ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.spacing_mut().item_spacing.x = 0.0;
|
|
||||||
ui.label("with love from ");
|
|
||||||
ui.hyperlink_to("futile", "http://futile.eu");
|
|
||||||
});
|
|
||||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
|
||||||
ui.label(format!(
|
|
||||||
"v{} {}",
|
|
||||||
env!("CARGO_PKG_VERSION"),
|
|
||||||
if cfg!(debug_assertions) { "(dev)" } else { "" }
|
|
||||||
));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
ui.add(egui::Image::new(egui::include_image!("../assets/icon.png")))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn show_debug(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) {
|
|
||||||
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_string(),
|
|
||||||
|(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(self);
|
|
||||||
self.empty_session.disabled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for App {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
let persistent_state = PersistentState {
|
|
||||||
launcher: self.launch.selected.clone(),
|
|
||||||
};
|
|
||||||
let config_path = CONFIG_PATH.as_path();
|
|
||||||
let config_path_parent = config_path.parent().unwrap();
|
|
||||||
if !config_path_parent.exists() {
|
|
||||||
fs::create_dir(config_path_parent).unwrap();
|
|
||||||
}
|
|
||||||
let mut config_file = File::create(config_path).unwrap();
|
|
||||||
let json = serde_json::to_string_pretty(&persistent_state).unwrap();
|
|
||||||
config_file.write_all(json.as_bytes()).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_icon() -> eframe::egui::IconData {
|
|
||||||
let icon = include_bytes!("../assets/icon.png");
|
|
||||||
let image = image::load_from_memory(icon).unwrap().into_rgba8();
|
|
||||||
let (width, height) = image.dimensions();
|
|
||||||
eframe::egui::IconData {
|
|
||||||
rgba: image.into_raw(),
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run() {
|
|
||||||
let options = eframe::NativeOptions {
|
|
||||||
viewport: egui::ViewportBuilder::default()
|
|
||||||
.with_resizable(false)
|
|
||||||
.with_maximize_button(false)
|
|
||||||
.with_inner_size([256.0, 232.0])
|
|
||||||
.with_icon(load_icon()),
|
|
||||||
centered: true,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
eframe::run_native(
|
|
||||||
"GTA Tools",
|
|
||||||
options,
|
|
||||||
Box::new(|_cc| Ok(Box::<App>::default())),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
+440
@@ -0,0 +1,440 @@
|
|||||||
|
mod persistent_state;
|
||||||
|
mod settings;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
features::{
|
||||||
|
self, anti_afk::AntiAfk, empty_session::EmptySession, force_close::ForceClose,
|
||||||
|
launch::Launch,
|
||||||
|
},
|
||||||
|
gui::{persistent_state::PersistentState, settings::Settings},
|
||||||
|
util::{
|
||||||
|
self,
|
||||||
|
consts::{APP_STORAGE_PATH, ENHANCED, GTA_WINDOW_TITLE, LEGACY},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use eframe::egui;
|
||||||
|
use std::{
|
||||||
|
fs,
|
||||||
|
io::Write,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
|
const WINDOW_SIZE: [f32; 2] = [240.0, 245.0];
|
||||||
|
|
||||||
|
#[derive(Debug, Default, PartialEq, Eq)]
|
||||||
|
pub enum Stage {
|
||||||
|
#[default]
|
||||||
|
Main,
|
||||||
|
Settings,
|
||||||
|
About,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct Flags {
|
||||||
|
elevated: bool,
|
||||||
|
debug: bool,
|
||||||
|
closing: bool,
|
||||||
|
current_frame: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct App {
|
||||||
|
settings: Settings,
|
||||||
|
stage: Stage,
|
||||||
|
flags: Flags,
|
||||||
|
pub sysinfo: sysinfo::System,
|
||||||
|
pub game_handle: windows::Win32::Foundation::HANDLE,
|
||||||
|
launch: Launch,
|
||||||
|
force_close: ForceClose,
|
||||||
|
empty_session: EmptySession,
|
||||||
|
anti_afk: AntiAfk,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for App {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
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 {
|
||||||
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||||
|
catppuccin_egui::set_theme(ctx, self.settings.theme.into());
|
||||||
|
self.run_timers();
|
||||||
|
egui::TopBottomPanel::bottom("bottom_panel")
|
||||||
|
.exact_height(25.0)
|
||||||
|
.show(ctx, |ui| {
|
||||||
|
ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| {
|
||||||
|
ui.selectable_value(&mut self.stage, Stage::Main, "Main");
|
||||||
|
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| {
|
||||||
|
let button = ui
|
||||||
|
.add_enabled(!self.flags.elevated, egui::Button::new("Elevate"))
|
||||||
|
.on_hover_text("Relaunch ourselves as administrator.")
|
||||||
|
.on_disabled_hover_text("We are already running elevated.");
|
||||||
|
if button.clicked() {
|
||||||
|
util::win::elevate(util::win::ElevationExitMethod::Gentle(
|
||||||
|
&mut self.flags.closing,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
egui::ScrollArea::vertical()
|
||||||
|
.auto_shrink([false, true])
|
||||||
|
.show(ui, |ui| match self.stage {
|
||||||
|
Stage::Main => {
|
||||||
|
self.show_game(ctx, ui);
|
||||||
|
self.show_session(ctx, ui);
|
||||||
|
self.show_network(ctx, ui);
|
||||||
|
}
|
||||||
|
Stage::Settings => self.show_settings(ctx, ui),
|
||||||
|
Stage::About => self.show_about(ctx, ui),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if check_debug_keycombo_pressed(ctx) {
|
||||||
|
self.flags.debug = !self.flags.debug;
|
||||||
|
}
|
||||||
|
if check_debug_viewport_close_button_pressed(ctx) {
|
||||||
|
self.flags.debug = false;
|
||||||
|
}
|
||||||
|
if self.flags.debug {
|
||||||
|
let main_rect = ctx.input(|i| {
|
||||||
|
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(load_icon()),
|
||||||
|
|ctx, _class| {
|
||||||
|
if 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 {
|
||||||
|
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
fn show_game(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) {
|
||||||
|
header(ui, "Game");
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui.button("Launch").clicked() {
|
||||||
|
features::launch::launch(&self.launch.selected);
|
||||||
|
}
|
||||||
|
build_combo_box::<features::launch::Platform>(ui, &mut self.launch.selected, "Launch");
|
||||||
|
});
|
||||||
|
let force_close_button = ui.add_sized(
|
||||||
|
[104.0, 0.0],
|
||||||
|
egui::Button::new(&self.force_close.button_text),
|
||||||
|
);
|
||||||
|
if force_close_button.clicked() && !self.force_close.prompting {
|
||||||
|
self.force_close.prompting();
|
||||||
|
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) {
|
||||||
|
header(ui, "Session");
|
||||||
|
ui.add_enabled_ui(!self.empty_session.disabled, |ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui.button("Empty current session").clicked() {
|
||||||
|
self.empty_session.interval = Instant::now();
|
||||||
|
self.empty_session.disabled = true;
|
||||||
|
features::empty_session::activate(self);
|
||||||
|
}
|
||||||
|
ui.label(&self.empty_session.countdown.i_string);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.checkbox(&mut self.anti_afk.enabled, "Anti AFK")
|
||||||
|
.on_hover_text("You should be tabbed in\nfor this to work.");
|
||||||
|
if self.anti_afk.enabled {
|
||||||
|
ui.add_space(8.0);
|
||||||
|
ui.add_enabled_ui(false, |ui| {
|
||||||
|
ui.label(if util::win::is_window_focused(GTA_WINDOW_TITLE) {
|
||||||
|
"GTA is focused."
|
||||||
|
} else {
|
||||||
|
"GTA is not focused!"
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if self.anti_afk.enabled && self.anti_afk.interval.elapsed() >= features::anti_afk::INTERVAL
|
||||||
|
{
|
||||||
|
self.anti_afk.activate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_network(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) {
|
||||||
|
header(ui, "Network");
|
||||||
|
egui::Frame::new()
|
||||||
|
.outer_margin(egui::vec2(0.0, -2.0))
|
||||||
|
.show(ui, |ui| {
|
||||||
|
let response = ui.add_enabled_ui(self.flags.elevated, |ui| {
|
||||||
|
let label = ui.label("Game's network access");
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
let available_width = label.rect.width();
|
||||||
|
let spacing = ui.spacing().item_spacing.x;
|
||||||
|
let button_width = (available_width - spacing) / 2.0;
|
||||||
|
if ui
|
||||||
|
.add_sized([button_width, 18.0], egui::Button::new("Block"))
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
features::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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
response.response.on_disabled_hover_text(
|
||||||
|
"This requires administrator.\nUse the Elevate button.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::unused_self)]
|
||||||
|
fn show_about(&self, _ctx: &egui::Context, ui: &mut egui::Ui) {
|
||||||
|
ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.spacing_mut().item_spacing.x = 0.0;
|
||||||
|
ui.label("with love from ");
|
||||||
|
ui.hyperlink_to("futile", "http://futile.eu");
|
||||||
|
});
|
||||||
|
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||||
|
ui.label(format!(
|
||||||
|
"v{} {}",
|
||||||
|
env!("CARGO_PKG_VERSION"),
|
||||||
|
if cfg!(debug_assertions) { "(dev)" } else { "" }
|
||||||
|
));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
ui.add(egui::Image::new(egui::include_image!(
|
||||||
|
"../../assets/icon.png"
|
||||||
|
)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_settings(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Theme");
|
||||||
|
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_string(),
|
||||||
|
|(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(self);
|
||||||
|
self.empty_session.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for App {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// save any persistent state to config file
|
||||||
|
let persistent_state = PersistentState {
|
||||||
|
launcher: self.launch.selected,
|
||||||
|
settings: self.settings.clone(),
|
||||||
|
};
|
||||||
|
persistent_state.set();
|
||||||
|
// make sure we are not suspending game
|
||||||
|
features::empty_session::deactivate(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn header(ui: &mut egui::Ui, text: &str) {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label(egui::RichText::new(text).font(egui::FontId::new(
|
||||||
|
12.5,
|
||||||
|
egui::FontFamily::Name("Ubuntu-Regular".into()),
|
||||||
|
)));
|
||||||
|
ui.add(egui::Separator::default().horizontal());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_combo_box<E>(ui: &mut egui::Ui, current_value: &mut E, label: impl std::hash::Hash)
|
||||||
|
where
|
||||||
|
E: strum::IntoEnumIterator + std::fmt::Display + std::cmp::PartialEq + Copy,
|
||||||
|
{
|
||||||
|
egui::ComboBox::from_id_salt(label)
|
||||||
|
.selected_text(current_value.to_string())
|
||||||
|
.show_ui(ui, |ui| {
|
||||||
|
for v in E::iter() {
|
||||||
|
ui.selectable_value(current_value, v, v.to_string());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_debug_keycombo_pressed(ctx: &egui::Context) -> bool {
|
||||||
|
ctx.input(|i| i.modifiers.all() && i.key_pressed(egui::Key::D))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_debug_viewport_close_button_pressed(ctx: &egui::Context) -> bool {
|
||||||
|
ctx.input(|i| {
|
||||||
|
i.raw
|
||||||
|
.viewports
|
||||||
|
.get(&egui::ViewportId::from_hash_of("debug_viewport"))
|
||||||
|
.filter(|vp| vp.close_requested())
|
||||||
|
.is_some()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_icon() -> egui::IconData {
|
||||||
|
let icon = include_bytes!("../../assets/icon.png");
|
||||||
|
let image = image::load_from_memory(icon).unwrap().into_rgba8();
|
||||||
|
let (width, height) = image.dimensions();
|
||||||
|
egui::IconData {
|
||||||
|
rgba: image.into_raw(),
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)]
|
||||||
|
fn app_creator(
|
||||||
|
cc: &eframe::CreationContext<'_>,
|
||||||
|
) -> 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();
|
||||||
|
if let Some(persistent_state) = PersistentState::get() {
|
||||||
|
app.launch.selected = persistent_state.launcher;
|
||||||
|
app.settings = persistent_state.settings;
|
||||||
|
}
|
||||||
|
let elevated = util::win::is_elevated();
|
||||||
|
if app.settings.start_elevated && !elevated {
|
||||||
|
util::win::elevate(util::win::ElevationExitMethod::Forced);
|
||||||
|
}
|
||||||
|
app.flags.elevated = elevated;
|
||||||
|
egui_extras::install_image_loaders(&cc.egui_ctx);
|
||||||
|
cc.egui_ctx.style_mut(|style| {
|
||||||
|
style.spacing.item_spacing = egui::vec2(4.0, 4.0);
|
||||||
|
style.interaction.selectable_labels = false;
|
||||||
|
});
|
||||||
|
let mut fonts = egui::FontDefinitions::default();
|
||||||
|
fonts.font_data.insert(
|
||||||
|
"Ubuntu-Regular".to_string(),
|
||||||
|
egui::FontData::from_static(include_bytes!("../../assets/Ubuntu-Regular.ttf")).into(),
|
||||||
|
);
|
||||||
|
fonts.families.insert(
|
||||||
|
egui::FontFamily::Name("Ubuntu-Regular".into()),
|
||||||
|
vec!["Ubuntu-Regular".to_string()],
|
||||||
|
);
|
||||||
|
cc.egui_ctx.set_fonts(fonts);
|
||||||
|
Ok(app)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run() {
|
||||||
|
let options = eframe::NativeOptions {
|
||||||
|
viewport: egui::ViewportBuilder::default()
|
||||||
|
.with_resizable(false)
|
||||||
|
.with_maximize_button(false)
|
||||||
|
.with_inner_size(WINDOW_SIZE)
|
||||||
|
.with_icon(load_icon()),
|
||||||
|
centered: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
eframe::run_native("GTA Tools", options, Box::new(app_creator)).unwrap();
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
use crate::{features::launch::Platform, gui::settings::Settings, util::consts::APP_STORAGE_PATH};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{
|
||||||
|
fs::{self, File},
|
||||||
|
io::Write,
|
||||||
|
path::PathBuf,
|
||||||
|
sync::LazyLock,
|
||||||
|
};
|
||||||
|
|
||||||
|
static CONFIG_PATH: LazyLock<PathBuf> = LazyLock::new(|| APP_STORAGE_PATH.join("config.json"));
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct PersistentState {
|
||||||
|
pub launcher: Platform,
|
||||||
|
pub settings: Settings,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PersistentState {
|
||||||
|
pub fn get() -> Option<Self> {
|
||||||
|
fs::read_to_string(CONFIG_PATH.as_path())
|
||||||
|
.ok()
|
||||||
|
.and_then(|config| serde_json::from_str::<Self>(&config).ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&self) {
|
||||||
|
let config_path = CONFIG_PATH.as_path();
|
||||||
|
let config_path_parent = config_path.parent().unwrap();
|
||||||
|
if !config_path_parent.exists() {
|
||||||
|
fs::create_dir(config_path_parent).unwrap();
|
||||||
|
}
|
||||||
|
let mut config_file = File::create(config_path).unwrap();
|
||||||
|
let json = serde_json::to_string_pretty(&self).unwrap();
|
||||||
|
config_file.write_all(json.as_bytes()).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fmt::Display;
|
||||||
|
use strum::EnumIter;
|
||||||
|
|
||||||
|
#[allow(clippy::enum_variant_names)]
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, EnumIter)]
|
||||||
|
pub enum Theme {
|
||||||
|
CatppuccinLatte,
|
||||||
|
CatppuccinFrappe,
|
||||||
|
CatppuccinMacchiato,
|
||||||
|
CatppuccinMocha,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Theme> for catppuccin_egui::Theme {
|
||||||
|
fn from(val: Theme) -> Self {
|
||||||
|
match val {
|
||||||
|
Theme::CatppuccinLatte => catppuccin_egui::LATTE,
|
||||||
|
Theme::CatppuccinFrappe => catppuccin_egui::FRAPPE,
|
||||||
|
Theme::CatppuccinMacchiato => catppuccin_egui::MACCHIATO,
|
||||||
|
Theme::CatppuccinMocha => catppuccin_egui::MOCHA,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)]
|
||||||
|
pub struct Settings {
|
||||||
|
pub theme: Theme,
|
||||||
|
pub start_elevated: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Settings {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
theme: Theme::CatppuccinMocha,
|
||||||
|
start_elevated: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +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 static APP_STORAGE_PATH: LazyLock<PathBuf> =
|
||||||
|
LazyLock::new(|| dirs::config_local_dir().unwrap().join("GTA Tools"));
|
||||||
|
|||||||
@@ -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();
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
use windows::{
|
|
||||||
Win32::Foundation::{CloseHandle, HANDLE},
|
|
||||||
Win32::Security::{GetTokenInformation, TOKEN_ELEVATION, TOKEN_QUERY, TokenElevation},
|
|
||||||
Win32::System::Threading::{GetCurrentProcess, OpenProcessToken},
|
|
||||||
Win32::UI::{Shell::ShellExecuteW, WindowsAndMessaging::SW_HIDE},
|
|
||||||
core::{HSTRING, PCWSTR},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn elevate(closing: &mut bool) {
|
|
||||||
let exe = std::env::current_exe().unwrap();
|
|
||||||
unsafe {
|
|
||||||
ShellExecuteW(
|
|
||||||
None,
|
|
||||||
&HSTRING::from("runas"),
|
|
||||||
&HSTRING::from(exe.as_path()),
|
|
||||||
PCWSTR::null(),
|
|
||||||
PCWSTR::null(),
|
|
||||||
SW_HIDE,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
*closing = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_elevated() -> bool {
|
|
||||||
unsafe {
|
|
||||||
let mut token: HANDLE = HANDLE::default();
|
|
||||||
if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token).is_ok() {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
pub mod consts;
|
pub mod consts;
|
||||||
pub mod countdown;
|
pub mod countdown;
|
||||||
pub mod elevation;
|
pub mod win;
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
use windows::{
|
||||||
|
Win32::{
|
||||||
|
Foundation::{CloseHandle, HANDLE},
|
||||||
|
Security::{GetTokenInformation, TOKEN_ELEVATION, TOKEN_QUERY, TokenElevation},
|
||||||
|
System::Threading::{GetCurrentProcess, OpenProcessToken},
|
||||||
|
UI::{
|
||||||
|
Input::KeyboardAndMouse::GetAsyncKeyState,
|
||||||
|
Shell::ShellExecuteW,
|
||||||
|
WindowsAndMessaging::{
|
||||||
|
CURSOR_SHOWING, CURSORINFO, GetCursorInfo, GetForegroundWindow, GetWindowTextW,
|
||||||
|
SW_NORMAL,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
core::{HSTRING, PCWSTR},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub enum ElevationExitMethod<'a> {
|
||||||
|
Gentle(&'a mut bool),
|
||||||
|
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)]
|
||||||
|
pub fn is_window_focused(target_title: &str) -> bool {
|
||||||
|
let mut buffer = [0; 512];
|
||||||
|
unsafe {
|
||||||
|
let hwnd = GetForegroundWindow();
|
||||||
|
let length = GetWindowTextW(hwnd, &mut buffer);
|
||||||
|
let current_title = String::from_utf16_lossy(&buffer[..length as usize]);
|
||||||
|
current_title == target_title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_any_key_pressed(keys: &[u8]) -> bool {
|
||||||
|
keys.iter()
|
||||||
|
.any(|&key| unsafe { (GetAsyncKeyState(i32::from(key)) & i16::MIN) != 0 })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn elevate(closing: ElevationExitMethod) {
|
||||||
|
let exe = std::env::current_exe().unwrap();
|
||||||
|
unsafe {
|
||||||
|
ShellExecuteW(
|
||||||
|
None,
|
||||||
|
&HSTRING::from("runas"),
|
||||||
|
&HSTRING::from(exe.as_path()),
|
||||||
|
PCWSTR::null(),
|
||||||
|
PCWSTR::null(),
|
||||||
|
SW_NORMAL,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
match closing {
|
||||||
|
ElevationExitMethod::Gentle(closing) => *closing = true,
|
||||||
|
ElevationExitMethod::Forced => std::process::exit(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_elevated() -> bool {
|
||||||
|
let mut token: HANDLE = HANDLE::default();
|
||||||
|
unsafe {
|
||||||
|
if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token).is_err() {
|
||||||
|
return 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
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user