12 Commits
10 changed files with 294 additions and 124 deletions
Generated
+1 -1
View File
@@ -812,7 +812,7 @@ dependencies = [
[[package]] [[package]]
name = "gta-tools" name = "gta-tools"
version = "0.10.0" version = "0.11.0"
dependencies = [ dependencies = [
"catppuccin-egui", "catppuccin-egui",
"eframe", "eframe",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "gta-tools" name = "gta-tools"
version = "0.10.0" version = "0.11.0"
edition = "2024" edition = "2024"
license = "Apache-2.0" license = "Apache-2.0"
authors = ["futile <git@futile.eu>"] authors = ["futile <git@futile.eu>"]
+6 -6
View File
@@ -3,8 +3,8 @@
A toolset of convenient things for GTA V Online. A toolset of convenient things for GTA V Online.
<picture> <picture>
<source srcset="https://i.vgy.me/M4sOHh.png" media="(prefers-color-scheme: dark)"> <source srcset="https://i.vgy.me/xt8EfK.png" media="(prefers-color-scheme: dark)">
<img src="https://i.vgy.me/mpO9uc.png"> <img src="https://i.vgy.me/lYlZnd.png">
</picture> </picture>
## Installing ## Installing
@@ -13,14 +13,14 @@ A toolset of convenient things for GTA V Online.
Download the latest release [here](https://github.com/elituf/gta-tools/releases/latest/download/gta-tools.exe) and place it somewhere convenient for you, such as Documents. You could then make a shortcut titled "GTA Tools", and pin it to taskbar or Start. Download the latest release [here](https://github.com/elituf/gta-tools/releases/latest/download/gta-tools.exe) and place it somewhere convenient for you, such as Documents. You could then make a shortcut titled "GTA Tools", and pin it to taskbar or Start.
**Option 2** — <ins>Build from source</ins> **Option 2** — <ins>Build from source</ins>
You will need the Rust toolchain, which can be obtained [here](https://rustup.rs). Follow the instructions of its installer. Once you have Rust installed, clone this repo and navigate to it. At this point, you should probably `git checkout x.x.x`, where `x.x.x` is the latest tag. You can then run `cargo build --release`. Once you do that, you can use the binary located at `.\target\release\gta-tools.exe` in the same way as **Option 1**. You will need Rust, which you can get [here](https://rustup.rs). Then, clone this repo and navigate to it. At this point, you should probably `git checkout x.x.x`, where `x.x.x` is the latest tag. You can then run `cargo build --release`. Once you do that, you can use the binary located at `.\target\release\gta-tools.exe` in the same way as **Option 1**.
It is recommended to always use an up-to-date version of GTA Tools from [releases](https://github.com/elituf/gta-tools/releases). You can also easily access this repository by going to the <kbd>About</kbd> page of GTA Tools and clicking the GitHub button beside the version number.
## Guide ## Guide
Every feature of GTA Tools is Legacy/Enhanced-agnostic. Some functionality of GTA Tools requires administrator access. If necessary, GTA Tools can either be started as admin manually, or, the user can simply use the <kbd>Elevate</kbd> button to relaunch GTA Tools as admin. Every feature of GTA Tools is Legacy/Enhanced-agnostic. Some functionality of GTA Tools requires administrator access. If necessary, GTA Tools can either be started as admin manually, or, the user can simply use the <kbd>Elevate</kbd> button to relaunch GTA Tools as admin.
It is recommended to always use an up-to-date version of GTA Tools from [releases](https://github.com/elituf/gta-tools/releases). You can also easily access this repository by going to the <kbd>About</kbd> page of GTA Tools and clicking the GitHub button beside the version number.
#### Game #### Game
This section is quite simple. This section is quite simple.
@@ -61,7 +61,7 @@ This feature is primarily useful for **replay glitching**, which is an exploit t
## Issues ## Issues
- I have noticed that on my current Windows 10 install, when not elevated (administrator), the <kbd>Force close game</kbd> and <kbd>Empty current session</kbd> features can fail due to being denied access. This was tested by other people and is not guaranteed to happen. If this does happen to you, I recommend using GTA Tools always in elevated mode. Check "Always start elevated" in the Settings tab. - It is possible that when not elevated (administrator), the <kbd>Force close game</kbd> and <kbd>Empty current session</kbd> features can fail due to being denied access to the game. This is not guaranteed to happen. If this does happen to you, I recommend always using GTA Tools in elevated mode. For convenience, you may check "Always start elevated" in the Settings tab.
+13
View File
@@ -0,0 +1,13 @@
### <ins>todo</ins>
- rewrite entire codebase
- add global hotkeys
### <ins>doing</ins>
- better error reporting
### <ins>done</ins>
- change `is_system_theme_dark` to `is_system_theme_light`
- fix weird changing colour on futile link in about
+111 -44
View File
@@ -1,9 +1,13 @@
use crate::util::{ use crate::{
gui::settings::BlockMethod,
util::{
consts::game::{EXE_ENHANCED, EXE_LEGACY}, consts::game::{EXE_ENHANCED, EXE_LEGACY},
system_info::SystemInfo, system_info::SystemInfo,
},
}; };
use std::{ use std::{
path::Path, error::Error,
path::{Path, PathBuf},
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use strum::{Display, EnumIter}; use strum::{Display, EnumIter};
@@ -11,7 +15,7 @@ use windows::{
Win32::{ Win32::{
NetworkManagement::WindowsFirewall::{ NetworkManagement::WindowsFirewall::{
INetFwPolicy2, INetFwRule, NET_FW_ACTION_BLOCK, NET_FW_IP_PROTOCOL_ANY, INetFwPolicy2, INetFwRule, NET_FW_ACTION_BLOCK, NET_FW_IP_PROTOCOL_ANY,
NET_FW_RULE_DIR_IN, NET_FW_RULE_DIR_OUT, NetFwPolicy2, NetFwRule, NET_FW_RULE_DIR_OUT, NetFwPolicy2, NetFwRule,
}, },
System::Com::{ System::Com::{
CLSCTX_INPROC_SERVER, COINIT_MULTITHREADED, CoCreateInstance, CoInitializeEx, CLSCTX_INPROC_SERVER, COINIT_MULTITHREADED, CoCreateInstance, CoInitializeEx,
@@ -21,8 +25,8 @@ use windows::{
core::BSTR, core::BSTR,
}; };
const FILTER_NAME_IN: &str = "[GTA Tools] Block all inbound traffic for GTA V"; const FILTER_NAME_EXE: &str = "[GTA Tools] Block outbound traffic for all of GTA V";
const FILTER_NAME_OUT: &str = "[GTA Tools] Block all outbound traffic for GTA V"; const FILTER_NAME_SAVE_SERVER: &str = "[GTA Tools] Block outbound traffic to Rockstar save server";
const INTERVAL: Duration = Duration::from_secs(3); const INTERVAL: Duration = Duration::from_secs(3);
@@ -54,7 +58,11 @@ pub struct GameNetworking {
impl Default for GameNetworking { impl Default for GameNetworking {
fn default() -> Self { fn default() -> Self {
Self { Self {
blocked_status: Self::is_blocked().into(), blocked_status: if Self::is_save_server_blocked().unwrap() {
Self::is_save_server_blocked().unwrap().into()
} else {
Self::is_exe_blocked().unwrap().into()
},
com_initialized: unsafe { CoInitializeEx(None, COINIT_MULTITHREADED) }.is_ok(), com_initialized: unsafe { CoInitializeEx(None, COINIT_MULTITHREADED) }.is_ok(),
timer: Instant::now(), timer: Instant::now(),
counting: false, counting: false,
@@ -70,53 +78,93 @@ impl Drop for GameNetworking {
} }
} }
enum Mode {
EntireGame(PathBuf),
SaveServer(String),
}
impl GameNetworking { impl GameNetworking {
pub fn block_all(&mut self, system_info: &mut SystemInfo) { fn block_generic(&self, mode: Mode) -> Result<(), Box<dyn Error>> {
let policy: INetFwPolicy2 =
unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER) }?;
let rules = unsafe { policy.Rules() }?;
let filter_name = match mode {
Mode::EntireGame(_) => FILTER_NAME_EXE,
Mode::SaveServer(_) => FILTER_NAME_SAVE_SERVER,
};
unsafe { rules.Remove(&BSTR::from(filter_name)) }?;
let rule: INetFwRule = unsafe { CoCreateInstance(&NetFwRule, None, CLSCTX_INPROC_SERVER) }?;
unsafe { rule.SetName(&BSTR::from(filter_name)) }?;
match mode {
Mode::EntireGame(exe_path) => {
let exe_path = BSTR::from(exe_path.to_string_lossy().to_string());
unsafe { rule.SetApplicationName(&exe_path) }?;
}
Mode::SaveServer(save_server_ip) => {
unsafe { rule.SetRemoteAddresses(&BSTR::from(save_server_ip)) }?;
}
}
unsafe { rule.SetDirection(NET_FW_RULE_DIR_OUT) }?;
unsafe { rule.SetEnabled(true.into()) }?;
unsafe { rule.SetAction(NET_FW_ACTION_BLOCK) }?;
unsafe { rule.SetProtocol(NET_FW_IP_PROTOCOL_ANY.0) }?;
unsafe { rules.Add(&rule) }?;
Ok(())
}
fn unblock_generic(&self, filter_name: &str) -> Result<(), Box<dyn Error>> {
let policy: INetFwPolicy2 =
unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER) }?;
let rules = unsafe { policy.Rules() }?;
unsafe { rules.Remove(&BSTR::from(filter_name)) }?;
Ok(())
}
fn is_blocked_generic(filter_name: &str) -> Result<bool, Box<dyn Error>> {
let policy: INetFwPolicy2 =
unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER) }?;
let rules = unsafe { policy.Rules() }?;
let rule_exists = unsafe { rules.Item(&BSTR::from(filter_name)) }.is_ok();
Ok(rule_exists)
}
pub fn block_exe(&mut self, system_info: &mut SystemInfo) -> Result<(), Box<dyn Error>> {
let Some(exe_path) = get_game_exe_path(system_info) else { let Some(exe_path) = get_game_exe_path(system_info) else {
self.blocked_status = BlockedStatus::Failed; self.blocked_status = BlockedStatus::Failed;
return; return Ok(());
}; };
let policy: INetFwPolicy2 = self.block_generic(Mode::EntireGame(exe_path.to_path_buf()))?;
unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER) }.unwrap(); self.blocked_status = Self::is_exe_blocked()?.into();
let rules = unsafe { policy.Rules() }.unwrap(); Ok(())
let exe_path = BSTR::from(exe_path.to_string_lossy().to_string());
for filter in [
(FILTER_NAME_IN, NET_FW_RULE_DIR_IN),
(FILTER_NAME_OUT, NET_FW_RULE_DIR_OUT),
] {
unsafe { rules.Remove(&BSTR::from(filter.0)) }.unwrap();
let rule: INetFwRule =
unsafe { CoCreateInstance(&NetFwRule, None, CLSCTX_INPROC_SERVER) }.unwrap();
unsafe { rule.SetName(&BSTR::from(filter.0)) }.unwrap();
unsafe { rule.SetApplicationName(&exe_path) }.unwrap();
unsafe { rule.SetDirection(filter.1) }.unwrap();
unsafe { rule.SetEnabled(true.into()) }.unwrap();
unsafe { rule.SetAction(NET_FW_ACTION_BLOCK) }.unwrap();
unsafe { rule.SetProtocol(NET_FW_IP_PROTOCOL_ANY.0) }.unwrap();
unsafe { rules.Add(&rule) }.unwrap();
}
self.blocked_status = Self::is_blocked().into();
} }
pub fn unblock_all(&mut self) { pub fn unblock_exe(&mut self) -> Result<(), Box<dyn Error>> {
let policy: INetFwPolicy2 = self.unblock_generic(FILTER_NAME_EXE)?;
unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER) }.unwrap(); self.blocked_status = Self::is_exe_blocked()?.into();
let rules = unsafe { policy.Rules() }.unwrap(); Ok(())
unsafe { rules.Remove(&BSTR::from(FILTER_NAME_IN)) }.unwrap();
unsafe { rules.Remove(&BSTR::from(FILTER_NAME_OUT)) }.unwrap();
self.blocked_status = Self::is_blocked().into();
} }
fn is_blocked() -> bool { fn is_exe_blocked() -> Result<bool, Box<dyn Error>> {
let policy: INetFwPolicy2 = Self::is_blocked_generic(FILTER_NAME_EXE)
unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER) }.unwrap();
let rules = unsafe { policy.Rules() }.unwrap();
let in_rule_exists = unsafe { rules.Item(&BSTR::from(FILTER_NAME_IN)) }.is_ok();
let out_rule_exists = unsafe { rules.Item(&BSTR::from(FILTER_NAME_OUT)) }.is_ok();
in_rule_exists || out_rule_exists
} }
pub fn if_failed_return_to_boolean(&mut self) { pub fn block_save_server(&mut self, save_server_ip: &str) -> Result<(), Box<dyn Error>> {
self.block_generic(Mode::SaveServer(save_server_ip.to_owned()))?;
self.blocked_status = Self::is_save_server_blocked()?.into();
Ok(())
}
pub fn unblock_save_server(&mut self) -> Result<(), Box<dyn Error>> {
self.unblock_generic(FILTER_NAME_SAVE_SERVER)?;
self.blocked_status = Self::is_save_server_blocked()?.into();
Ok(())
}
pub fn is_save_server_blocked() -> Result<bool, Box<dyn Error>> {
Self::is_blocked_generic(FILTER_NAME_SAVE_SERVER)
}
pub fn reset_indicator_if_failed(&mut self) {
if self.blocked_status == BlockedStatus::Failed && !self.counting { if self.blocked_status == BlockedStatus::Failed && !self.counting {
self.counting = true; self.counting = true;
self.timer = Instant::now(); self.timer = Instant::now();
@@ -126,7 +174,26 @@ impl GameNetworking {
&& self.timer.elapsed() >= INTERVAL && self.timer.elapsed() >= INTERVAL
{ {
self.counting = false; self.counting = false;
self.blocked_status = Self::is_blocked().into(); self.blocked_status = Self::is_exe_blocked().unwrap().into();
}
}
pub fn ensure_not_both_blocked_simultaneously(&mut self, block_method: BlockMethod) {
match block_method {
BlockMethod::EntireGame => {
if Self::is_save_server_blocked().unwrap() {
// ignoring the return because if this is an error the user can just thug it out at that point
let _ = self.unblock_save_server();
self.blocked_status = Self::is_exe_blocked().unwrap().into();
}
}
BlockMethod::SaveServer => {
if Self::is_exe_blocked().unwrap() {
// ignoring the return because if this is an error the user can just thug it out at that point
let _ = self.unblock_exe();
self.blocked_status = Self::is_save_server_blocked().unwrap().into();
}
}
} }
} }
} }
+85 -24
View File
@@ -1,6 +1,10 @@
use crate::{ use crate::{
features, features,
gui::{settings::Settings, tools, ui_ext::UiExt}, gui::{
settings::{BlockMethod, ROCKSTAR_SAVE_SERVER, Settings},
tools,
ui_ext::UiExt,
},
util::{ util::{
consts::{colours, game::WINDOW_TITLE}, consts::{colours, game::WINDOW_TITLE},
persistent_state::PersistentState, persistent_state::PersistentState,
@@ -156,15 +160,15 @@ impl App {
egui::Frame::new() egui::Frame::new()
.outer_margin(egui::vec2(0.0, -2.0)) .outer_margin(egui::vec2(0.0, -2.0))
.show(ui, |ui| { .show(ui, |ui| {
let response = ui.add_enabled_ui(self.flags.elevated, |ui| { ui.add_enabled_ui(self.flags.elevated, |ui| {
let label = ui.horizontal(|ui| { let label = ui.horizontal(|ui| {
let label = ui.label("Game's network access"); let label = match self.settings.block_method {
BlockMethod::EntireGame => ui.label("Game's network access"),
BlockMethod::SaveServer => ui.label("Rockstar save server access"),
};
ui.add_space(1.0); ui.add_space(1.0);
ui.create_indicator_dot(self.game_networking.blocked_status) ui.create_indicator_dot(self.game_networking.blocked_status);
.on_hover_text( self.game_networking.reset_indicator_if_failed();
"This turns yellow if GTA Tools\ncannot find your game.",
);
self.game_networking.if_failed_return_to_boolean();
label label
}); });
ui.horizontal(|ui| { ui.horizontal(|ui| {
@@ -175,19 +179,36 @@ impl App {
.add_sized([button_width, 18.0], egui::Button::new("Block")) .add_sized([button_width, 18.0], egui::Button::new("Block"))
.clicked() .clicked()
{ {
self.game_networking.block_all(&mut self.system_info); match self.settings.block_method {
BlockMethod::EntireGame => {
self.game_networking
.block_exe(&mut self.system_info)
.unwrap();
}
BlockMethod::SaveServer => {
self.game_networking
.block_save_server(&self.settings.save_server_ip)
.unwrap();
}
}
} }
if ui if ui
.add_sized([button_width, 18.0], egui::Button::new("Unblock")) .add_sized([button_width, 18.0], egui::Button::new("Unblock"))
.clicked() .clicked()
{ {
self.game_networking.unblock_all(); match self.settings.block_method {
BlockMethod::EntireGame => {
self.game_networking.unblock_exe().unwrap();
}
BlockMethod::SaveServer => {
self.game_networking.unblock_save_server().unwrap();
}
}
} }
}); });
}); })
response.response.on_disabled_hover_text( .response
"This requires administrator.\nUse the Elevate button.", .on_disabled_hover_text("This requires administrator.\nUse the Elevate button.");
);
}); });
} }
@@ -198,17 +219,10 @@ impl App {
} }
fn show_settings_stage(&mut self, ctx: &egui::Context, ui: &mut egui::Ui) { fn show_settings_stage(&mut self, ctx: &egui::Context, ui: &mut egui::Ui) {
ui.horizontal(|ui| { ui.collapsing("General", |ui| {
ui.label("Launch version"); ui.checkbox(&mut self.settings.start_elevated, "Always start elevated");
egui::ComboBox::from_id_salt("Launch version")
.selected_text(self.settings.launch_version.to_string())
.show_ui(ui, |ui| {
ui.build_menu(&mut self.settings.launch_version);
});
});
ui.horizontal(|ui| { ui.horizontal(|ui| {
let selection = self.settings.theme; let selection = self.settings.theme;
ui.label("Theme");
egui::ComboBox::from_id_salt("Theme") egui::ComboBox::from_id_salt("Theme")
.selected_text(self.settings.theme.to_string()) .selected_text(self.settings.theme.to_string())
.show_ui(ui, |ui| { .show_ui(ui, |ui| {
@@ -217,8 +231,51 @@ 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());
} }
ui.label("Theme");
});
});
ui.collapsing("Game", |ui| {
ui.horizontal(|ui| {
egui::ComboBox::from_id_salt("Launch version")
.selected_text(self.settings.launch_version.to_string())
.show_ui(ui, |ui| {
ui.build_menu(&mut self.settings.launch_version);
});
ui.label("Launch version");
});
});
ui.collapsing("Network", |ui| {
ui.add_enabled_ui(self.flags.elevated, |ui| {
ui.horizontal(|ui| {
egui::ComboBox::from_id_salt("Block method")
.selected_text(self.settings.block_method.to_string())
.show_ui(ui, |ui| {
ui.build_menu(&mut self.settings.block_method);
});
ui.label("Block method");
self.game_networking
.ensure_not_both_blocked_simultaneously(self.settings.block_method);
});
ui.horizontal(|ui| {
ui.add_enabled_ui(
self.settings.block_method == BlockMethod::SaveServer,
|ui| {
ui.add(
egui::TextEdit::singleline(&mut self.settings.save_server_ip)
.char_limit(15)
.desired_width(92.0),
);
ui.label("Save server IP");
if ui.button("").clicked() {
self.settings.save_server_ip = String::from(ROCKSTAR_SAVE_SERVER);
}
},
);
});
})
.response
.on_disabled_hover_text("This requires administrator.\nUse the Elevate button.");
}); });
ui.checkbox(&mut self.settings.start_elevated, "Always start elevated");
} }
fn show_about_stage(&self, _ctx: &egui::Context, ui: &mut egui::Ui) { fn show_about_stage(&self, _ctx: &egui::Context, ui: &mut egui::Ui) {
@@ -232,8 +289,12 @@ impl App {
ui.label(""); ui.label("");
}); });
ui.label(" from "); ui.label(" from ");
ui.scope(|ui| {
ui.style_mut().visuals.hyperlink_color =
catppuccin_egui::Theme::from(self.settings.theme).text;
ui.hyperlink_to("futile", "https://futile.eu"); ui.hyperlink_to("futile", "https://futile.eu");
}); });
});
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
ui.style_mut().spacing.button_padding = egui::Vec2::new(4.0, 0.0); ui.style_mut().spacing.button_padding = egui::Vec2::new(4.0, 0.0);
if ui.button("").on_hover_text("View source code").clicked() { if ui.button("").on_hover_text("View source code").clicked() {
+57 -32
View File
@@ -2,6 +2,41 @@ use crate::util::win;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use strum::{Display, EnumIter}; use strum::{Display, EnumIter};
pub const ROCKSTAR_SAVE_SERVER: &str = "192.81.241.171";
#[derive(Clone, Copy, Debug, Default, Display, PartialEq, Eq, Serialize, Deserialize, EnumIter)]
pub enum Theme {
#[default]
#[strum(to_string = "Auto")]
Auto,
#[strum(to_string = "Latte")]
Latte,
#[strum(to_string = "Frappe")]
Frappe,
#[strum(to_string = "Macchiato")]
Macchiato,
#[strum(to_string = "Mocha")]
Mocha,
}
impl From<Theme> for catppuccin_egui::Theme {
fn from(val: Theme) -> Self {
match val {
Theme::Auto => {
if win::is_system_theme_light() {
catppuccin_egui::LATTE
} else {
catppuccin_egui::MOCHA
}
}
Theme::Latte => catppuccin_egui::LATTE,
Theme::Frappe => catppuccin_egui::FRAPPE,
Theme::Macchiato => catppuccin_egui::MACCHIATO,
Theme::Mocha => catppuccin_egui::MOCHA,
}
}
}
#[derive(Clone, Copy, Debug, Default, Display, PartialEq, Eq, Serialize, Deserialize, EnumIter)] #[derive(Clone, Copy, Debug, Default, Display, PartialEq, Eq, Serialize, Deserialize, EnumIter)]
pub enum LaunchVersion { pub enum LaunchVersion {
#[default] #[default]
@@ -10,41 +45,31 @@ pub enum LaunchVersion {
} }
#[derive(Clone, Copy, Debug, Default, Display, PartialEq, Eq, Serialize, Deserialize, EnumIter)] #[derive(Clone, Copy, Debug, Default, Display, PartialEq, Eq, Serialize, Deserialize, EnumIter)]
pub enum Theme { pub enum BlockMethod {
#[default] #[default]
#[strum(to_string = "Auto")] #[strum(to_string = "Entire game")]
Auto, EntireGame,
#[strum(to_string = "Catppuccin Latte")] #[strum(to_string = "Save server")]
CatppuccinLatte, SaveServer,
#[strum(to_string = "Catppuccin Frappe")]
CatppuccinFrappe,
#[strum(to_string = "Catppuccin Macchiato")]
CatppuccinMacchiato,
#[strum(to_string = "Catppuccin Mocha")]
CatppuccinMocha,
} }
impl From<Theme> for catppuccin_egui::Theme { #[derive(Clone, Debug, Serialize, Deserialize)]
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,
Theme::CatppuccinMocha => catppuccin_egui::MOCHA,
}
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Settings { pub struct Settings {
pub launch_version: LaunchVersion,
pub theme: Theme,
pub start_elevated: bool, pub start_elevated: bool,
pub theme: Theme,
pub launch_version: LaunchVersion,
pub save_server_ip: String,
pub block_method: BlockMethod,
}
impl Default for Settings {
fn default() -> Self {
Self {
start_elevated: false,
theme: Theme::default(),
launch_version: LaunchVersion::default(),
block_method: BlockMethod::default(),
save_server_ip: String::from(ROCKSTAR_SAVE_SERVER),
}
}
} }
+3 -1
View File
@@ -4,6 +4,8 @@ mod features;
mod gui; mod gui;
mod util; mod util;
use std::fmt::Write;
fn init_storage() { fn init_storage() {
if !util::consts::path::APP_STORAGE.exists() { if !util::consts::path::APP_STORAGE.exists() {
std::fs::create_dir_all(util::consts::path::APP_STORAGE.as_path()).unwrap(); std::fs::create_dir_all(util::consts::path::APP_STORAGE.as_path()).unwrap();
@@ -14,7 +16,7 @@ fn panic_hook(panic_info: &std::panic::PanicHookInfo<'_>) {
let backtrace = std::backtrace::Backtrace::capture(); let backtrace = std::backtrace::Backtrace::capture();
let mut message = format!("{panic_info}"); let mut message = format!("{panic_info}");
if backtrace.status() == std::backtrace::BacktraceStatus::Captured { if backtrace.status() == std::backtrace::BacktraceStatus::Captured {
message += &format!("\nstack backtrace:\n{backtrace}"); write!(message, "\nstack backtrace:\n{backtrace}").unwrap();
} }
log::error!("{message}"); log::error!("{message}");
} }
+1 -1
View File
@@ -59,6 +59,6 @@ impl Logger {
humantime::format_rfc3339_seconds(SystemTime::now()), humantime::format_rfc3339_seconds(SystemTime::now()),
record.level(), record.level(),
record.args() record.args()
) );
} }
} }
+5 -3
View File
@@ -80,7 +80,7 @@ pub fn is_elevated() -> bool {
result.is_ok() && elevation.TokenIsElevated != 0 result.is_ok() && elevation.TokenIsElevated != 0
} }
pub fn is_system_theme_dark() -> bool { pub fn is_system_theme_light() -> bool {
use winreg::RegKey; use winreg::RegKey;
let hkcu = RegKey::predef(winreg::enums::HKEY_CURRENT_USER); let hkcu = RegKey::predef(winreg::enums::HKEY_CURRENT_USER);
let Ok(subkey) = let Ok(subkey) =
@@ -88,8 +88,10 @@ pub fn is_system_theme_dark() -> bool {
else { else {
return true; return true;
}; };
let Ok(dword): Result<u32, std::io::Error> = subkey.get_value("AppsUseLightTheme") else { let Ok(apps_use_light_theme): Result<u32, std::io::Error> =
subkey.get_value("AppsUseLightTheme")
else {
return true; return true;
}; };
dword != 1 apps_use_light_theme == 1
} }