Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8943e61f3a
|
||
|
|
ed0a828fd4
|
||
|
|
96ef494443
|
||
|
|
9b5340a6c5
|
||
|
|
74132ec322
|
||
|
|
a58606c91d
|
||
|
|
47adf42a48
|
||
|
|
0fb587027c
|
||
|
|
0424b7b114
|
||
|
|
e89c3acc06
|
||
|
|
ab64c29935
|
||
|
|
e216445c7f
|
Generated
+1
-1
@@ -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
@@ -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>"]
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
+113
-46
@@ -1,9 +1,13 @@
|
|||||||
use crate::util::{
|
use crate::{
|
||||||
consts::game::{EXE_ENHANCED, EXE_LEGACY},
|
gui::settings::BlockMethod,
|
||||||
system_info::SystemInfo,
|
util::{
|
||||||
|
consts::game::{EXE_ENHANCED, EXE_LEGACY},
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+94
-33
@@ -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,27 +219,63 @@ 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")
|
ui.horizontal(|ui| {
|
||||||
.selected_text(self.settings.launch_version.to_string())
|
let selection = self.settings.theme;
|
||||||
.show_ui(ui, |ui| {
|
egui::ComboBox::from_id_salt("Theme")
|
||||||
ui.build_menu(&mut self.settings.launch_version);
|
.selected_text(self.settings.theme.to_string())
|
||||||
});
|
.show_ui(ui, |ui| {
|
||||||
|
ui.build_menu(&mut self.settings.theme);
|
||||||
|
});
|
||||||
|
if selection != self.settings.theme {
|
||||||
|
catppuccin_egui::set_theme(ctx, self.settings.theme.into());
|
||||||
|
}
|
||||||
|
ui.label("Theme");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
ui.horizontal(|ui| {
|
ui.collapsing("Game", |ui| {
|
||||||
let selection = self.settings.theme;
|
ui.horizontal(|ui| {
|
||||||
ui.label("Theme");
|
egui::ComboBox::from_id_salt("Launch version")
|
||||||
egui::ComboBox::from_id_salt("Theme")
|
.selected_text(self.settings.launch_version.to_string())
|
||||||
.selected_text(self.settings.theme.to_string())
|
.show_ui(ui, |ui| {
|
||||||
.show_ui(ui, |ui| {
|
ui.build_menu(&mut self.settings.launch_version);
|
||||||
ui.build_menu(&mut self.settings.theme);
|
});
|
||||||
|
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);
|
||||||
});
|
});
|
||||||
if selection != self.settings.theme {
|
ui.horizontal(|ui| {
|
||||||
catppuccin_egui::set_theme(ctx, self.settings.theme.into());
|
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,7 +289,11 @@ impl App {
|
|||||||
ui.label("❤");
|
ui.label("❤");
|
||||||
});
|
});
|
||||||
ui.label(" from ");
|
ui.label(" from ");
|
||||||
ui.hyperlink_to("futile", "https://futile.eu");
|
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.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);
|
||||||
|
|||||||
+57
-32
@@ -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 {
|
pub struct Settings {
|
||||||
match val {
|
pub start_elevated: bool,
|
||||||
Theme::Auto => {
|
pub theme: Theme,
|
||||||
if win::is_system_theme_dark() {
|
pub launch_version: LaunchVersion,
|
||||||
catppuccin_egui::MOCHA
|
pub save_server_ip: String,
|
||||||
} else {
|
pub block_method: BlockMethod,
|
||||||
catppuccin_egui::LATTE
|
}
|
||||||
}
|
|
||||||
}
|
impl Default for Settings {
|
||||||
Theme::CatppuccinLatte => catppuccin_egui::LATTE,
|
fn default() -> Self {
|
||||||
Theme::CatppuccinFrappe => catppuccin_egui::FRAPPE,
|
Self {
|
||||||
Theme::CatppuccinMacchiato => catppuccin_egui::MACCHIATO,
|
start_elevated: false,
|
||||||
Theme::CatppuccinMocha => catppuccin_egui::MOCHA,
|
theme: Theme::default(),
|
||||||
|
launch_version: LaunchVersion::default(),
|
||||||
|
block_method: BlockMethod::default(),
|
||||||
|
save_server_ip: String::from(ROCKSTAR_SAVE_SERVER),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
|
||||||
pub struct Settings {
|
|
||||||
pub launch_version: LaunchVersion,
|
|
||||||
pub theme: Theme,
|
|
||||||
pub start_elevated: bool,
|
|
||||||
}
|
|
||||||
|
|||||||
+3
-1
@@ -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
@@ -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
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user