From 27524e855a25393c2aef900ac767ed8bdbce279b Mon Sep 17 00:00:00 2001 From: futile Date: Sat, 19 Apr 2025 04:28:24 +0100 Subject: [PATCH] numerous improvements in gui and feature code --- src/features/empty_session.rs | 28 +++- src/features/force_close.rs | 30 +++- src/features/launch.rs | 19 +-- src/gui/app.rs | 272 ++++++++++++++-------------------- src/gui/run.rs | 2 + src/gui/settings.rs | 27 +--- src/gui/tools.rs | 16 +- 7 files changed, 176 insertions(+), 218 deletions(-) diff --git a/src/features/empty_session.rs b/src/features/empty_session.rs index 89bde17..9fd4c17 100644 --- a/src/features/empty_session.rs +++ b/src/features/empty_session.rs @@ -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")] unsafe extern "system" { pub unsafe fn NtSuspendProcess(ProcessHandle: HANDLE) -> NTSTATUS; @@ -46,22 +60,22 @@ fn get_gta_pid(sysinfo: &mut System) -> u32 { 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); if pid == u32::MAX { return; } unsafe { - *handle = OpenProcess(PROCESS_SUSPEND_RESUME, false, pid).unwrap(); - let _ = NtSuspendProcess(*handle); + *game_handle = OpenProcess(PROCESS_SUSPEND_RESUME, false, pid).unwrap(); + let _ = NtSuspendProcess(*game_handle); } } -pub fn deactivate(handle: &mut HANDLE) { +pub fn deactivate(game_handle: &mut HANDLE) { unsafe { - if !handle.is_invalid() { - let _ = NtResumeProcess(*handle); - let _ = CloseHandle(*handle); + if !game_handle.is_invalid() { + let _ = NtResumeProcess(*game_handle); + let _ = CloseHandle(*game_handle); } } } diff --git a/src/features/force_close.rs b/src/features/force_close.rs index fa904da..4efa8a9 100644 --- a/src/features/force_close.rs +++ b/src/features/force_close.rs @@ -1,12 +1,15 @@ use crate::util::consts::game::{EXE_ENHANCED, EXE_LEGACY}; -use std::time::Instant; +use std::time::{Duration, Instant}; use sysinfo::System; +pub const INTERVAL: Duration = Duration::from_secs(3); + #[derive(Debug)] pub struct ForceClose { pub button_text: String, pub prompting: bool, pub interval: Instant, + current_frame: bool, } impl Default for ForceClose { @@ -15,15 +18,32 @@ impl Default for ForceClose { button_text: "Force close game".to_owned(), prompting: false, interval: Instant::now(), + current_frame: false, } } } impl ForceClose { - pub fn prompting(&mut self) { - self.button_text = "Are you sure?".to_owned(); - self.prompting = true; - self.interval = Instant::now(); + pub fn prompt(&mut self, force_close_button_clicked: bool, sysinfo: &mut System) { + if force_close_button_clicked && !self.prompting { + *self = Self { + 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; + } } } diff --git a/src/features/launch.rs b/src/features/launch.rs index 39955c0..11191d3 100644 --- a/src/features/launch.rs +++ b/src/features/launch.rs @@ -1,27 +1,18 @@ use serde::{Deserialize, Serialize}; -use std::{fmt::Display, path::PathBuf, process::Command}; -use strum::EnumIter; +use std::{path::PathBuf, process::Command}; +use strum::{Display, EnumIter}; 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 { #[default] Steam, + #[strum(to_string = "Rockstar Games")] Rockstar, + #[strum(to_string = "Epic Games")] 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)] pub struct Launch { pub selected: Platform, diff --git a/src/gui/app.rs b/src/gui/app.rs index 4c92b74..8d4f3ed 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -1,12 +1,6 @@ use crate::{ - features::{ - self, anti_afk::AntiAfk, empty_session::EmptySession, force_close::ForceClose, - launch::Launch, - }, - gui::{ - settings::{self, Settings}, - tools, - }, + features, + gui::{settings::Settings, tools}, util::{ self, consts::{ @@ -19,10 +13,11 @@ use crate::{ }; use eframe::egui; use std::time::{Duration, Instant}; +use strum::{Display, EnumIter}; 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 { #[default] Main, @@ -35,10 +30,9 @@ pub struct Flags { pub elevated: bool, debug: bool, closing: bool, - current_frame: bool, } -#[derive(Debug)] +#[derive(Debug, Default)] pub struct App { meta: Meta, pub settings: Settings, @@ -46,40 +40,20 @@ pub struct App { pub flags: Flags, pub sysinfo: sysinfo::System, pub game_handle: windows::Win32::Foundation::HANDLE, - pub launch: Launch, - force_close: ForceClose, - empty_session: EmptySession, - 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(), - } - } + pub launch: features::launch::Launch, + force_close: features::force_close::ForceClose, + empty_session: features::empty_session::EmptySession, + anti_afk: features::anti_afk::AntiAfk, } 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(); + ctx.request_repaint_after(Duration::from_millis(100)); 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"); + tools::build_menu::(ui, &mut self.stage); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { let button = ui .add_enabled(!self.flags.elevated, egui::Button::new("Elevate")) @@ -97,53 +71,17 @@ impl eframe::App for App { 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), + Stage::Main => self.show_main_stage(ctx, ui), + Stage::Settings => self.show_settings_stage(ctx, ui), + Stage::About => self.show_about_stage(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; } - if tools::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(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); - }); - }); - }, - ); + self.show_debug_viewport(ctx); } - ctx.request_repaint_after(Duration::from_millis(100)); if self.flags.closing { ctx.send_viewport_cmd(egui::ViewportCommand::Close); } @@ -151,45 +89,32 @@ impl eframe::App for 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"); ui.horizontal(|ui| { if ui.button("Launch").clicked() { features::launch::launch(&self.launch.selected); } - tools::build_combo_box::( - ui, - &mut self.launch.selected, - "Launch", - ); + egui::ComboBox::from_id_salt("Launch") + .selected_text(self.launch.selected.to_string()) + .width(120.0) + .show_ui(ui, |ui| { + tools::build_menu(ui, &mut self.launch.selected); + }); }); 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; - } + self.force_close + .prompt(force_close_button.clicked(), &mut self.sysinfo); } - 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"); ui.add_enabled_ui(!self.empty_session.disabled, |ui| { ui.horizontal(|ui| { + self.empty_session.run_timers(&mut self.game_handle); if ui.button("Empty current session").clicked() { self.empty_session.interval = Instant::now(); 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"); egui::Frame::new() .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.horizontal(|ui| { ui.horizontal(|ui| { @@ -285,63 +232,66 @@ impl App { }); } - fn show_settings(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) { - ui.horizontal(|ui| { - ui.label("Theme"); - tools::build_combo_box::(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("version", |ui| { - 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.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() == 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:#?}"))); - } - - 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; - } + 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| { + if ui.button("open storage path").clicked() { + open::that_detached(APP_STORAGE_PATH.as_path()).unwrap(); + } + ui.collapsing("version", |ui| { + 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.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() == 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:#?}"))); + }); + }); + }, + ); } } diff --git a/src/gui/run.rs b/src/gui/run.rs index afb7bf8..0f05c3a 100644 --- a/src/gui/run.rs +++ b/src/gui/run.rs @@ -35,7 +35,9 @@ fn app_creator( util::win::elevate(util::win::ElevationExitMethod::Forced); } app.flags.elevated = elevated; + app.sysinfo.refresh_all(); 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| { style.spacing.item_spacing = egui::vec2(4.0, 4.0); style.interaction.selectable_labels = false; diff --git a/src/gui/settings.rs b/src/gui/settings.rs index 543554b..c4f3fe2 100644 --- a/src/gui/settings.rs +++ b/src/gui/settings.rs @@ -1,12 +1,15 @@ use serde::{Deserialize, Serialize}; -use std::fmt::Display; -use strum::EnumIter; +use strum::{Display, EnumIter}; -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, EnumIter)] +#[derive(Clone, Copy, Debug, Display, PartialEq, Eq, Serialize, Deserialize, EnumIter)] pub enum Theme { + #[strum(to_string = "Catppuccin Latte")] CatppuccinLatte, + #[strum(to_string = "Catppuccin Frappe")] CatppuccinFrappe, + #[strum(to_string = "Catppuccin Macchiato")] CatppuccinMacchiato, + #[strum(to_string = "Catppuccin Mocha")] CatppuccinMocha, } @@ -21,24 +24,6 @@ impl From 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)] pub struct Settings { pub theme: Theme, diff --git a/src/gui/tools.rs b/src/gui/tools.rs index ca189c7..c12b48e 100644 --- a/src/gui/tools.rs +++ b/src/gui/tools.rs @@ -21,24 +21,20 @@ pub fn header(ui: &mut egui::Ui, text: &str) { }); } -pub fn build_combo_box(ui: &mut egui::Ui, current_value: &mut E, label: impl std::hash::Hash) +pub fn build_menu(ui: &mut egui::Ui, current_value: &mut E) 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| { - E::iter().for_each(|v| { - ui.selectable_value(current_value, v, v.to_string()); - }); - }); + E::iter().for_each(|variant| { + ui.selectable_value(current_value, variant, variant.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)) } -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| { i.raw .viewports