numerous improvements in gui and feature code

This commit is contained in:
2025-04-19 04:28:24 +01:00
parent 0257834d04
commit 27524e855a
7 changed files with 176 additions and 218 deletions
+21 -7
View File
@@ -28,6 +28,20 @@ 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; pub unsafe fn NtSuspendProcess(ProcessHandle: HANDLE) -> NTSTATUS;
@@ -46,22 +60,22 @@ fn get_gta_pid(sysinfo: &mut System) -> 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);
} }
} }
} }
+25 -5
View File
@@ -1,12 +1,15 @@
use crate::util::consts::game::{EXE_ENHANCED, EXE_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;
pub 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, pub prompting: bool,
pub interval: Instant, pub interval: Instant,
current_frame: bool,
} }
impl Default for ForceClose { impl Default for ForceClose {
@@ -15,15 +18,32 @@ impl Default for ForceClose {
button_text: "Force close game".to_owned(), button_text: "Force close game".to_owned(),
prompting: false, prompting: false,
interval: Instant::now(), interval: Instant::now(),
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) {
self.button_text = "Are you sure?".to_owned(); if force_close_button_clicked && !self.prompting {
self.prompting = true; *self = Self {
self.interval = Instant::now(); button_text: "Are you sure?".to_owned(),
prompting: true,
interval: Instant::now(),
current_frame: true,
}
}
if self.prompting && self.interval.elapsed() <= INTERVAL {
if force_close_button_clicked && !self.current_frame {
activate(sysinfo);
*self = Self::default();
}
} else {
*self = Self::default();
}
if self.current_frame {
self.current_frame = false;
}
} }
} }
+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,
+74 -124
View File
@@ -1,12 +1,6 @@
use crate::{ use crate::{
features::{ features,
self, anti_afk::AntiAfk, empty_session::EmptySession, force_close::ForceClose, gui::{settings::Settings, tools},
launch::Launch,
},
gui::{
settings::{self, Settings},
tools,
},
util::{ util::{
self, self,
consts::{ consts::{
@@ -19,10 +13,11 @@ use crate::{
}; };
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 { pub enum Stage {
#[default] #[default]
Main, Main,
@@ -35,10 +30,9 @@ pub struct Flags {
pub elevated: bool, pub elevated: bool,
debug: bool, debug: bool,
closing: bool, closing: bool,
current_frame: bool,
} }
#[derive(Debug)] #[derive(Debug, Default)]
pub struct App { pub struct App {
meta: Meta, meta: Meta,
pub settings: Settings, pub settings: Settings,
@@ -46,40 +40,20 @@ pub struct App {
pub flags: Flags, pub flags: Flags,
pub sysinfo: sysinfo::System, pub sysinfo: sysinfo::System,
pub game_handle: windows::Win32::Foundation::HANDLE, pub game_handle: windows::Win32::Foundation::HANDLE,
pub launch: Launch, pub launch: features::launch::Launch,
force_close: ForceClose, force_close: features::force_close::ForceClose,
empty_session: EmptySession, empty_session: features::empty_session::EmptySession,
anti_afk: AntiAfk, anti_afk: features::anti_afk::AntiAfk,
}
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();
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"))
@@ -97,53 +71,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);
} }
@@ -151,45 +89,32 @@ 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| {
self.empty_session.run_timers(&mut self.game_handle);
if ui.button("Empty current session").clicked() { if ui.button("Empty current session").clicked() {
self.empty_session.interval = Instant::now(); self.empty_session.interval = Instant::now();
self.empty_session.disabled = true; self.empty_session.disabled = true;
@@ -218,7 +143,7 @@ impl App {
} }
} }
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))
@@ -249,7 +174,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| {
@@ -285,15 +232,26 @@ impl App {
}); });
} }
fn show_settings(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) { fn show_debug_viewport(&mut self, ctx: &egui::Context) {
ui.horizontal(|ui| { let main = ctx.input(|i| i.viewport().outer_rect.unwrap_or(egui::Rect::EVERYTHING));
ui.label("Theme"); let builder = egui::ViewportBuilder::default()
tools::build_combo_box::<settings::Theme>(ui, &mut self.settings.theme, "Theme"); .with_title("GTA Tools Debug")
}); .with_minimize_button(false)
ui.checkbox(&mut self.settings.start_elevated, "Always start elevated"); .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| {
fn show_debug(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) { egui::ScrollArea::both()
.auto_shrink([false, true])
.show(ui, |ui| {
if ui.button("open storage path").clicked() { if ui.button("open storage path").clicked() {
open::that_detached(APP_STORAGE_PATH.as_path()).unwrap(); open::that_detached(APP_STORAGE_PATH.as_path()).unwrap();
} }
@@ -330,18 +288,10 @@ impl App {
ui.label(format!("gta pid: {pid}")); ui.label(format!("gta pid: {pid}"));
}); });
ui.collapsing("app state", |ui| ui.label(format!("{self:#?}"))); 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;
}
} }
} }
+2
View File
@@ -35,7 +35,9 @@ fn app_creator(
util::win::elevate(util::win::ElevationExitMethod::Forced); util::win::elevate(util::win::ElevationExitMethod::Forced);
} }
app.flags.elevated = elevated; app.flags.elevated = elevated;
app.sysinfo.refresh_all();
egui_extras::install_image_loaders(&cc.egui_ctx); egui_extras::install_image_loaders(&cc.egui_ctx);
catppuccin_egui::set_theme(&cc.egui_ctx, app.settings.theme.into());
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;
+6 -21
View File
@@ -1,12 +1,15 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Display; use strum::{Display, EnumIter};
use strum::EnumIter;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, EnumIter)] #[derive(Clone, Copy, Debug, Display, 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,
} }
@@ -21,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