8 Commits
10 changed files with 133 additions and 89 deletions
Generated
+5 -3
View File
@@ -755,7 +755,7 @@ dependencies = [
[[package]] [[package]]
name = "gta-tools" name = "gta-tools"
version = "0.5.4" version = "0.6.0"
dependencies = [ dependencies = [
"catppuccin-egui", "catppuccin-egui",
"eframe", "eframe",
@@ -1230,11 +1230,13 @@ dependencies = [
[[package]] [[package]]
name = "nyquest" name = "nyquest"
version = "0.1.0" version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbec44acc8915e966540e83c40b4d57134a861ea2f519e6a7ab883bfbbc7ddaa" checksum = "3a4722c65afdcbb836197802c2f5dc050825964bebbd9802086e54aef39208b0"
dependencies = [ dependencies = [
"nyquest-interface", "nyquest-interface",
"serde",
"serde_json",
"thiserror 2.0.12", "thiserror 2.0.12",
] ]
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "gta-tools" name = "gta-tools"
version = "0.5.4" version = "0.6.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
@@ -16,7 +16,7 @@ egui_extras = { version = "0.31.1", default-features = false, features = [
"svg", "svg",
] } ] }
image = { version = "0.25.6", default-features = false, features = ["png"] } image = { version = "0.25.6", default-features = false, features = ["png"] }
nyquest = { version = "0.1.0", features = ["blocking"] } nyquest = { version = "0.1.1", features = ["blocking", "json"] }
nyquest-backend-winrt = { version = "0.1.0", features = ["blocking"] } nyquest-backend-winrt = { version = "0.1.0", features = ["blocking"] }
open = "5.3.2" open = "5.3.2"
semver = "1.0.26" semver = "1.0.26"
+1 -1
View File
@@ -44,7 +44,7 @@ impl ForceClose {
*self = Self::default(); *self = Self::default();
} }
fn finish_current_frame(&mut self) { const fn finish_current_frame(&mut self) {
if self.current_frame { if self.current_frame {
self.current_frame = false; self.current_frame = false;
} }
+12 -1
View File
@@ -1,6 +1,10 @@
use crate::{ use crate::{
features, features,
gui::{settings::Settings, tools, ui_ext::UiExt}, gui::{
settings::{Settings, Theme},
tools,
ui_ext::UiExt,
},
util::{consts::game::WINDOW_TITLE, meta::Meta, persistent_state::PersistentState, win}, util::{consts::game::WINDOW_TITLE, meta::Meta, persistent_state::PersistentState, win},
}; };
use eframe::egui; use eframe::egui;
@@ -201,6 +205,13 @@ impl App {
if selection != self.settings.theme { if selection != self.settings.theme {
catppuccin_egui::set_theme(ctx, self.settings.theme.into()); catppuccin_egui::set_theme(ctx, self.settings.theme.into());
} }
if ui
.add_visible(self.settings.theme == Theme::Auto, egui::Button::new(""))
.on_hover_text("Refresh theme")
.clicked()
{
catppuccin_egui::set_theme(ctx, self.settings.theme.into());
}
}); });
ui.checkbox(&mut self.settings.start_elevated, "Always start elevated"); ui.checkbox(&mut self.settings.start_elevated, "Always start elevated");
} }
+57 -57
View File
@@ -12,6 +12,62 @@ use crate::{
use eframe::egui; use eframe::egui;
impl App { impl App {
fn add_debug_viewport_contents(&mut self, ui: &mut egui::Ui) {
ui.collapsing("misc", |ui| {
if ui.button("open storage path").clicked() {
open::that_detached(APP_STORAGE_PATH.as_path()).unwrap();
}
ui.checkbox(
&mut self.meta.newer_version_available,
"spoof new version available",
)
.on_hover_text("(this could already be checked if\nthere actually IS a new version)");
ui.scope(|ui| {
use windows::Win32::UI::WindowsAndMessaging::{
GetForegroundWindow, GetWindowTextW,
};
let mut buffer = [0; 512];
let current_title = unsafe {
let hwnd = GetForegroundWindow();
let length = GetWindowTextW(hwnd, &mut buffer);
String::from_utf16_lossy(&buffer[..length as usize])
};
ui.label(format!("focused: \"{current_title}\""));
});
ui.horizontal(|ui| {
ui.label("blocked_status");
egui::ComboBox::from_id_salt("blocked_status")
.selected_text(self.game_networking.blocked_status.to_string())
.show_ui(ui, |ui| {
ui.build_menu(&mut self.game_networking.blocked_status);
});
});
});
ui.collapsing("anti afk", |ui| {
ui.label(format!(
"timer: {}",
self.anti_afk.interval.elapsed().as_secs()
));
ui.label(format!("can activate: {}", self.anti_afk.can_activate()));
});
ui.collapsing("sysinfo", |ui| {
if ui.button("refresh all").clicked() {
self.sysinfo.refresh_all();
}
let pid = self
.sysinfo
.processes()
.iter()
.find(|(_, p)| p.name() == EXE_ENHANCED || p.name() == EXE_LEGACY)
.map_or_else(
|| "no pid found!".to_owned(),
|(pid, _)| pid.as_u32().to_string(),
);
ui.label(format!("gta pid: {pid}"));
});
ui.collapsing("app state", |ui| ui.label(format!("{self:#?}")));
}
pub fn show_debug_viewport(&mut self, ctx: &egui::Context) { pub fn show_debug_viewport(&mut self, ctx: &egui::Context) {
let main = ctx.input(|i| i.viewport().outer_rect.unwrap_or(egui::Rect::EVERYTHING)); let main = ctx.input(|i| i.viewport().outer_rect.unwrap_or(egui::Rect::EVERYTHING));
let builder = egui::ViewportBuilder::default() let builder = egui::ViewportBuilder::default()
@@ -31,63 +87,7 @@ impl App {
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {
egui::ScrollArea::both() egui::ScrollArea::both()
.auto_shrink([false, true]) .auto_shrink([false, true])
.show(ui, |ui| { .show(ui, |ui| self.add_debug_viewport_contents(ui));
ui.collapsing("misc", |ui| {
if ui.button("open storage path").clicked() {
open::that_detached(APP_STORAGE_PATH.as_path()).unwrap();
}
ui.checkbox(
&mut self.meta.newer_version_available,
"spoof new version available",
)
.on_hover_text("(this could already be checked if\nthere actually IS a new version)");
ui.scope(|ui| {
use windows::Win32::UI::WindowsAndMessaging::{GetForegroundWindow, GetWindowTextW};
let mut buffer = [0; 512];
let current_title = unsafe {
let hwnd = GetForegroundWindow();
let length = GetWindowTextW(hwnd, &mut buffer);
String::from_utf16_lossy(&buffer[..length as usize])
};
ui.label(format!("focused: \"{current_title}\""));
});
ui.horizontal(|ui| {
ui.label("blocked_status");
egui::ComboBox::from_id_salt("blocked_status")
.selected_text(&self.game_networking.blocked_status.to_string())
.show_ui(ui, |ui| {
ui.build_menu(&mut self.game_networking.blocked_status);
});
});
});
ui.collapsing("anti afk", |ui| {
ui.label(format!(
"timer: {}",
self.anti_afk.interval.elapsed().as_secs()
));
ui.label(format!(
"can activate: {}",
self.anti_afk.can_activate()
));
});
ui.collapsing("sysinfo", |ui| {
if ui.button("refresh all").clicked() {
self.sysinfo.refresh_all();
}
let pid = self
.sysinfo
.processes()
.iter()
.find(|(_, p)| p.name() == EXE_ENHANCED || p.name() == EXE_LEGACY)
.map_or_else(
|| "no pid found!".to_owned(),
|(pid, _)| pid.as_u32().to_string(),
);
ui.label(format!("gta pid: {pid}"));
});
ui.collapsing("app state", |ui| ui.label(format!("{self:#?}")));
});
}); });
}, },
); );
+8 -6
View File
@@ -3,7 +3,7 @@ use crate::{
app::{App, WINDOW_SIZE}, app::{App, WINDOW_SIZE},
tools, tools,
}, },
util::{self, consts::APP_STORAGE_PATH, persistent_state::PersistentState}, util::{consts::APP_STORAGE_PATH, persistent_state::PersistentState, win},
}; };
use eframe::egui; use eframe::egui;
use std::{ use std::{
@@ -23,7 +23,7 @@ fn panic_hook(panic_info: &std::panic::PanicHookInfo<'_>) {
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
.unwrap() .unwrap()
.as_secs(); .as_secs();
let backtrace = std::backtrace::Backtrace::force_capture(); let backtrace = std::backtrace::Backtrace::capture();
let message = format!("[{timestamp}]\n{panic_info}\nstack backtrace:\n{backtrace}\n"); let message = format!("[{timestamp}]\n{panic_info}\nstack backtrace:\n{backtrace}\n");
file.write_all(message.as_bytes()).unwrap(); file.write_all(message.as_bytes()).unwrap();
} }
@@ -36,7 +36,7 @@ fn app_creator(
// initialize http client (nyquest) for windows // initialize http client (nyquest) for windows
nyquest_backend_winrt::register(); nyquest_backend_winrt::register();
// initialize App early to modify some things before returning it // initialize App early to modify some things before returning it
let mut app = Box::<App>::default(); let mut app = Box::new(App::default());
// load previously selected launch platform & settings from persistent state // load previously selected launch platform & settings from persistent state
if let Some(persistent_state) = PersistentState::get() { if let Some(persistent_state) = PersistentState::get() {
app.launch.selected = persistent_state.launcher; app.launch.selected = persistent_state.launcher;
@@ -44,16 +44,18 @@ fn app_creator(
} }
// check if we're elevated. if not, and the user wants an elevated launch - relaunch elevated // check if we're elevated. if not, and the user wants an elevated launch - relaunch elevated
if !app.flags.elevated && app.settings.start_elevated { if !app.flags.elevated && app.settings.start_elevated {
util::win::elevate(util::win::ElevationExitMethod::Forced); win::elevate(win::ElevationExitMethod::Forced);
} }
// refresh sysinfo because it initializes with nothing // refresh sysinfo because it initializes with nothing
app.sysinfo.refresh_all(); app.sysinfo.refresh_all();
// enable image loading support in egui // enable image loading support in egui
egui_extras::install_image_loaders(&cc.egui_ctx); egui_extras::install_image_loaders(&cc.egui_ctx);
// set our initial theme, from earlier loaded settings // set our initial theme, from earlier loaded settings. we set the egui theme
// to dark here to work around system theme based switching of the egui style
cc.egui_ctx.set_theme(egui::Theme::Dark);
catppuccin_egui::set_theme(&cc.egui_ctx, app.settings.theme.into()); catppuccin_egui::set_theme(&cc.egui_ctx, app.settings.theme.into());
// apply some global styling that we like // apply some global styling that we like
cc.egui_ctx.style_mut(|style| { cc.egui_ctx.all_styles_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;
}); });
+13 -11
View File
@@ -1,8 +1,12 @@
use crate::util::win;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use strum::{Display, EnumIter}; use strum::{Display, EnumIter};
#[derive(Clone, Copy, Debug, Display, PartialEq, Eq, Serialize, Deserialize, EnumIter)] #[derive(Clone, Copy, Debug, Default, Display, PartialEq, Eq, Serialize, Deserialize, EnumIter)]
pub enum Theme { pub enum Theme {
#[default]
#[strum(to_string = "Auto")]
Auto,
#[strum(to_string = "Catppuccin Latte")] #[strum(to_string = "Catppuccin Latte")]
CatppuccinLatte, CatppuccinLatte,
#[strum(to_string = "Catppuccin Frappe")] #[strum(to_string = "Catppuccin Frappe")]
@@ -16,6 +20,13 @@ pub enum Theme {
impl From<Theme> for catppuccin_egui::Theme { impl From<Theme> for catppuccin_egui::Theme {
fn from(val: Theme) -> Self { fn from(val: Theme) -> Self {
match val { match val {
Theme::Auto => {
if win::is_system_theme_dark() {
catppuccin_egui::MOCHA
} else {
catppuccin_egui::LATTE
}
}
Theme::CatppuccinLatte => catppuccin_egui::LATTE, Theme::CatppuccinLatte => catppuccin_egui::LATTE,
Theme::CatppuccinFrappe => catppuccin_egui::FRAPPE, Theme::CatppuccinFrappe => catppuccin_egui::FRAPPE,
Theme::CatppuccinMacchiato => catppuccin_egui::MACCHIATO, Theme::CatppuccinMacchiato => catppuccin_egui::MACCHIATO,
@@ -24,17 +35,8 @@ impl From<Theme> for catppuccin_egui::Theme {
} }
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Settings { pub struct Settings {
pub theme: Theme, pub theme: Theme,
pub start_elevated: bool, pub start_elevated: bool,
} }
impl Default for Settings {
fn default() -> Self {
Self {
theme: Theme::CatppuccinMocha,
start_elevated: false,
}
}
}
+18 -6
View File
@@ -1,5 +1,6 @@
use nyquest::{ClientBuilder, blocking::Request}; use nyquest::{ClientBuilder, blocking::Request};
use semver::Version; use semver::Version;
use serde::Deserialize;
const APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); const APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
const CODEBERG_ENDPOINT_ROOT: &str = "https://codeberg.org/api/v1"; const CODEBERG_ENDPOINT_ROOT: &str = "https://codeberg.org/api/v1";
@@ -13,12 +14,23 @@ pub struct Release {
impl Default for Release { impl Default for Release {
fn default() -> Self { fn default() -> Self {
Self { Self {
version: Version::parse(env!("CARGO_PKG_VERSION")).unwrap(), version: Version::new(0, 0, 0),
download_url: String::new(), download_url: String::new(),
} }
} }
} }
#[derive(Deserialize)]
struct LatestRelease {
tag_name: String,
assets: Vec<Asset>,
}
#[derive(Deserialize)]
struct Asset {
browser_download_url: String,
}
pub fn get_latest_release() -> Option<Release> { pub fn get_latest_release() -> Option<Release> {
let request_url = format!("{CODEBERG_ENDPOINT_ROOT}/repos/futile/gta-tools/releases/latest"); let request_url = format!("{CODEBERG_ENDPOINT_ROOT}/repos/futile/gta-tools/releases/latest");
let client = ClientBuilder::default() let client = ClientBuilder::default()
@@ -26,11 +38,11 @@ pub fn get_latest_release() -> Option<Release> {
.build_blocking() .build_blocking()
.unwrap(); .unwrap();
let response = client.request(Request::get(request_url)).unwrap(); let response = client.request(Request::get(request_url)).unwrap();
let json = serde_json::from_slice::<serde_json::Value>(&response.bytes().unwrap()).unwrap(); let json = response.json::<LatestRelease>().ok()?;
let tag_name = json["tag_name"].as_str()?; let tag_name = json.tag_name;
let browser_download_url = json["assets"][0]["browser_download_url"].as_str()?; let browser_download_url = &json.assets[0].browser_download_url;
Some(Release { Some(Release {
version: Version::parse(tag_name).expect("expected a valid semver pattern"), version: Version::parse(&tag_name).ok()?,
download_url: String::from(browser_download_url), download_url: browser_download_url.to_owned(),
}) })
} }
+3 -2
View File
@@ -1,7 +1,8 @@
use std::{env, path::PathBuf, sync::LazyLock}; use std::{env, path::PathBuf, sync::LazyLock};
pub static APP_STORAGE_PATH: LazyLock<PathBuf> = pub static APP_STORAGE_PATH: LazyLock<PathBuf> = LazyLock::new(|| {
LazyLock::new(|| PathBuf::from(env::var("LOCALAPPDATA").unwrap()).join("GTA Tools")); PathBuf::from(env::var("LOCALAPPDATA").unwrap_or_else(|_| String::from("."))).join("GTA Tools")
});
pub mod game { pub mod game {
pub const EXE_ENHANCED: &str = "GTA5_Enhanced.exe"; pub const EXE_ENHANCED: &str = "GTA5_Enhanced.exe";
+14
View File
@@ -83,3 +83,17 @@ pub fn is_elevated() -> bool {
result.is_ok() && elevation.TokenIsElevated != 0 result.is_ok() && elevation.TokenIsElevated != 0
} }
} }
pub fn is_system_theme_dark() -> bool {
use winreg::RegKey;
let hkcu = RegKey::predef(winreg::enums::HKEY_CURRENT_USER);
let Ok(subkey) =
hkcu.open_subkey("Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize")
else {
return true;
};
let Ok(dword): Result<u32, std::io::Error> = subkey.get_value("AppsUseLightTheme") else {
return true;
};
dword != 1
}