Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3158f71fb | ||
|
|
070e477fd4 | ||
|
|
7745bba10e | ||
|
|
8d7a0d12b9 | ||
|
|
b325ebb03c | ||
|
|
38c6402a12 | ||
|
|
2d42c0c3c4 | ||
|
|
a423721aee |
Generated
+5
-3
@@ -755,7 +755,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "gta-tools"
|
||||
version = "0.5.4"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"catppuccin-egui",
|
||||
"eframe",
|
||||
@@ -1230,11 +1230,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nyquest"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbec44acc8915e966540e83c40b4d57134a861ea2f519e6a7ab883bfbbc7ddaa"
|
||||
checksum = "3a4722c65afdcbb836197802c2f5dc050825964bebbd9802086e54aef39208b0"
|
||||
dependencies = [
|
||||
"nyquest-interface",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "gta-tools"
|
||||
version = "0.5.4"
|
||||
version = "0.6.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
@@ -16,7 +16,7 @@ egui_extras = { version = "0.31.1", default-features = false, features = [
|
||||
"svg",
|
||||
] }
|
||||
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"] }
|
||||
open = "5.3.2"
|
||||
semver = "1.0.26"
|
||||
|
||||
@@ -44,7 +44,7 @@ impl ForceClose {
|
||||
*self = Self::default();
|
||||
}
|
||||
|
||||
fn finish_current_frame(&mut self) {
|
||||
const fn finish_current_frame(&mut self) {
|
||||
if self.current_frame {
|
||||
self.current_frame = false;
|
||||
}
|
||||
|
||||
+12
-1
@@ -1,6 +1,10 @@
|
||||
use crate::{
|
||||
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},
|
||||
};
|
||||
use eframe::egui;
|
||||
@@ -201,6 +205,13 @@ impl App {
|
||||
if selection != self.settings.theme {
|
||||
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");
|
||||
}
|
||||
|
||||
+57
-57
@@ -12,6 +12,62 @@ use crate::{
|
||||
use eframe::egui;
|
||||
|
||||
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) {
|
||||
let main = ctx.input(|i| i.viewport().outer_rect.unwrap_or(egui::Rect::EVERYTHING));
|
||||
let builder = egui::ViewportBuilder::default()
|
||||
@@ -31,63 +87,7 @@ impl App {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
egui::ScrollArea::both()
|
||||
.auto_shrink([false, true])
|
||||
.show(ui, |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:#?}")));
|
||||
});
|
||||
.show(ui, |ui| self.add_debug_viewport_contents(ui));
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
+8
-6
@@ -3,7 +3,7 @@ use crate::{
|
||||
app::{App, WINDOW_SIZE},
|
||||
tools,
|
||||
},
|
||||
util::{self, consts::APP_STORAGE_PATH, persistent_state::PersistentState},
|
||||
util::{consts::APP_STORAGE_PATH, persistent_state::PersistentState, win},
|
||||
};
|
||||
use eframe::egui;
|
||||
use std::{
|
||||
@@ -23,7 +23,7 @@ fn panic_hook(panic_info: &std::panic::PanicHookInfo<'_>) {
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.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");
|
||||
file.write_all(message.as_bytes()).unwrap();
|
||||
}
|
||||
@@ -36,7 +36,7 @@ fn app_creator(
|
||||
// initialize http client (nyquest) for windows
|
||||
nyquest_backend_winrt::register();
|
||||
// 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
|
||||
if let Some(persistent_state) = PersistentState::get() {
|
||||
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
|
||||
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
|
||||
app.sysinfo.refresh_all();
|
||||
// enable image loading support in egui
|
||||
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());
|
||||
// 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.interaction.selectable_labels = false;
|
||||
});
|
||||
|
||||
+13
-11
@@ -1,8 +1,12 @@
|
||||
use crate::util::win;
|
||||
use serde::{Deserialize, Serialize};
|
||||
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 {
|
||||
#[default]
|
||||
#[strum(to_string = "Auto")]
|
||||
Auto,
|
||||
#[strum(to_string = "Catppuccin Latte")]
|
||||
CatppuccinLatte,
|
||||
#[strum(to_string = "Catppuccin Frappe")]
|
||||
@@ -16,6 +20,13 @@ pub enum Theme {
|
||||
impl From<Theme> for catppuccin_egui::Theme {
|
||||
fn from(val: Theme) -> Self {
|
||||
match val {
|
||||
Theme::Auto => {
|
||||
if win::is_system_theme_dark() {
|
||||
catppuccin_egui::MOCHA
|
||||
} else {
|
||||
catppuccin_egui::LATTE
|
||||
}
|
||||
}
|
||||
Theme::CatppuccinLatte => catppuccin_egui::LATTE,
|
||||
Theme::CatppuccinFrappe => catppuccin_egui::FRAPPE,
|
||||
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 theme: Theme,
|
||||
pub start_elevated: bool,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
theme: Theme::CatppuccinMocha,
|
||||
start_elevated: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+18
-6
@@ -1,5 +1,6 @@
|
||||
use nyquest::{ClientBuilder, blocking::Request};
|
||||
use semver::Version;
|
||||
use serde::Deserialize;
|
||||
|
||||
const APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
|
||||
const CODEBERG_ENDPOINT_ROOT: &str = "https://codeberg.org/api/v1";
|
||||
@@ -13,12 +14,23 @@ pub struct Release {
|
||||
impl Default for Release {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
version: Version::parse(env!("CARGO_PKG_VERSION")).unwrap(),
|
||||
version: Version::new(0, 0, 0),
|
||||
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> {
|
||||
let request_url = format!("{CODEBERG_ENDPOINT_ROOT}/repos/futile/gta-tools/releases/latest");
|
||||
let client = ClientBuilder::default()
|
||||
@@ -26,11 +38,11 @@ pub fn get_latest_release() -> Option<Release> {
|
||||
.build_blocking()
|
||||
.unwrap();
|
||||
let response = client.request(Request::get(request_url)).unwrap();
|
||||
let json = serde_json::from_slice::<serde_json::Value>(&response.bytes().unwrap()).unwrap();
|
||||
let tag_name = json["tag_name"].as_str()?;
|
||||
let browser_download_url = json["assets"][0]["browser_download_url"].as_str()?;
|
||||
let json = response.json::<LatestRelease>().ok()?;
|
||||
let tag_name = json.tag_name;
|
||||
let browser_download_url = &json.assets[0].browser_download_url;
|
||||
Some(Release {
|
||||
version: Version::parse(tag_name).expect("expected a valid semver pattern"),
|
||||
download_url: String::from(browser_download_url),
|
||||
version: Version::parse(&tag_name).ok()?,
|
||||
download_url: browser_download_url.to_owned(),
|
||||
})
|
||||
}
|
||||
|
||||
+3
-2
@@ -1,7 +1,8 @@
|
||||
use std::{env, path::PathBuf, sync::LazyLock};
|
||||
|
||||
pub static APP_STORAGE_PATH: LazyLock<PathBuf> =
|
||||
LazyLock::new(|| PathBuf::from(env::var("LOCALAPPDATA").unwrap()).join("GTA Tools"));
|
||||
pub static APP_STORAGE_PATH: LazyLock<PathBuf> = LazyLock::new(|| {
|
||||
PathBuf::from(env::var("LOCALAPPDATA").unwrap_or_else(|_| String::from("."))).join("GTA Tools")
|
||||
});
|
||||
|
||||
pub mod game {
|
||||
pub const EXE_ENHANCED: &str = "GTA5_Enhanced.exe";
|
||||
|
||||
@@ -83,3 +83,17 @@ pub fn is_elevated() -> bool {
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user