Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d7dde929d | ||
|
|
3ab802fd56 | ||
|
|
53030cc5f7 | ||
|
|
383224c2fa | ||
|
|
575a6a4f1d | ||
|
|
17579de277 | ||
|
|
00727b9913 | ||
|
|
1f6fc22525 | ||
|
|
27b09be76e | ||
|
|
6f15e8c348 | ||
|
|
d294aa9861 | ||
|
|
f688f1c3c4 | ||
|
|
58ec9f6567 | ||
|
|
1c9ce2a4d3 | ||
|
|
8a5d6cf38c | ||
|
|
d789b0f62d | ||
|
|
6079a72f47 | ||
|
|
914ffe46d2 | ||
|
|
935703e1ab | ||
|
|
202554e03b | ||
|
|
da2b15a0b6 | ||
|
|
ccc27b1944 | ||
|
|
10168e84d1 |
Generated
+78
-1504
File diff suppressed because it is too large
Load Diff
+10
-5
@@ -1,16 +1,22 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "gta-tools"
|
name = "gta-tools"
|
||||||
version = "0.5.0"
|
version = "0.5.3"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
catppuccin-egui = { version = "5.5.0", default-features = false, features = [
|
catppuccin-egui = { version = "5.5.0", default-features = false, features = [
|
||||||
"egui31",
|
"egui31",
|
||||||
] }
|
] }
|
||||||
chrono = "0.4.40"
|
chrono = { version = "0.4.40", default-features = false, features = ["clock"] }
|
||||||
dirs = "6.0.0"
|
dirs = "6.0.0"
|
||||||
eframe = "0.31.1"
|
eframe = { version = "0.31.1", default-features = false, features = [
|
||||||
egui_extras = { version = "0.31.1", features = ["image", "svg"] }
|
"glow",
|
||||||
|
"default_fonts",
|
||||||
|
] }
|
||||||
|
egui_extras = { version = "0.31.1", default-features = false, features = [
|
||||||
|
"image",
|
||||||
|
"svg",
|
||||||
|
] }
|
||||||
image = { version = "0.25.6", default-features = false, features = ["png"] }
|
image = { version = "0.25.6", default-features = false, features = ["png"] }
|
||||||
open = "5.3.2"
|
open = "5.3.2"
|
||||||
semver = "1.0.26"
|
semver = "1.0.26"
|
||||||
@@ -34,6 +40,5 @@ static_vcruntime = "2.0.0"
|
|||||||
winresource = "0.1.20"
|
winresource = "0.1.20"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
strip = true
|
|
||||||
lto = true
|
lto = true
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
|||||||
@@ -1,4 +1,13 @@
|
|||||||
# GTA Tools
|
# GTA Tools
|
||||||
A toolset of convenient things for GTA 5 Online.
|
|
||||||
|
|
||||||

|
A toolset of convenient things for GTA V Online.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Installing
|
||||||
|
|
||||||
|
**Option 1** — <ins>Download</ins>
|
||||||
|
Download the latest release [here](https://codeberg.org/futile/gta-tools/releases/download/latest/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>
|
||||||
|
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**.
|
||||||
|
|||||||
+21
-24
@@ -1,8 +1,8 @@
|
|||||||
use crate::util::{self, consts::game::WINDOW_TITLE};
|
use crate::util::{self, consts::game::WINDOW_TITLE};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use windows::Win32::UI::Input::KeyboardAndMouse::{
|
use windows::Win32::UI::Input::KeyboardAndMouse::{
|
||||||
KEYBD_EVENT_FLAGS, MAP_VIRTUAL_KEY_TYPE, MapVirtualKeyW, VIRTUAL_KEY, VK_NUMPAD4, VK_NUMPAD6,
|
INPUT, INPUT_KEYBOARD, KEYBD_EVENT_FLAGS, KEYBDINPUT, KEYEVENTF_KEYUP, MAPVK_VK_TO_VSC,
|
||||||
keybd_event,
|
MapVirtualKeyW, SendInput, VIRTUAL_KEY, VK_NUMPAD4, VK_NUMPAD6,
|
||||||
};
|
};
|
||||||
|
|
||||||
const INTERVAL: Duration = Duration::from_secs(60);
|
const INTERVAL: Duration = Duration::from_secs(60);
|
||||||
@@ -40,26 +40,23 @@ impl AntiAfk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn send(vk_codes: &[VIRTUAL_KEY]) {
|
fn send(vk_codes: &[VIRTUAL_KEY]) {
|
||||||
vk_codes.iter().for_each(|vk_code| unsafe {
|
let mut inputs = Vec::new();
|
||||||
keybd_event(
|
for &vk_code in vk_codes {
|
||||||
vk_code.0 as u8,
|
let scan_code = unsafe { MapVirtualKeyW(u32::from(vk_code.0), MAPVK_VK_TO_VSC) as u16 };
|
||||||
u8::try_from(MapVirtualKeyW(
|
for event in [KEYBD_EVENT_FLAGS(0), KEYEVENTF_KEYUP] {
|
||||||
u32::from(vk_code.0),
|
let mut input = INPUT {
|
||||||
MAP_VIRTUAL_KEY_TYPE(0),
|
r#type: INPUT_KEYBOARD,
|
||||||
))
|
..Default::default()
|
||||||
.unwrap(),
|
};
|
||||||
KEYBD_EVENT_FLAGS(0),
|
input.Anonymous.ki = KEYBDINPUT {
|
||||||
0,
|
wVk: vk_code,
|
||||||
);
|
wScan: scan_code,
|
||||||
keybd_event(
|
dwFlags: event,
|
||||||
vk_code.0 as u8,
|
time: 0,
|
||||||
u8::try_from(MapVirtualKeyW(
|
dwExtraInfo: 0,
|
||||||
u32::from(vk_code.0),
|
};
|
||||||
MAP_VIRTUAL_KEY_TYPE(0),
|
inputs.push(input);
|
||||||
))
|
}
|
||||||
.unwrap(),
|
unsafe { SendInput(&inputs, size_of::<INPUT>() as i32) };
|
||||||
KEYBD_EVENT_FLAGS(2),
|
}
|
||||||
0,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-16
@@ -7,8 +7,8 @@ const INTERVAL: Duration = Duration::from_secs(3);
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ForceClose {
|
pub struct ForceClose {
|
||||||
pub button_text: String,
|
pub button_text: String,
|
||||||
prompting: bool,
|
timer: Instant,
|
||||||
interval: Instant,
|
counting: bool,
|
||||||
current_frame: bool,
|
current_frame: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -16,8 +16,8 @@ impl Default for ForceClose {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
button_text: "Force close game".to_owned(),
|
button_text: "Force close game".to_owned(),
|
||||||
prompting: false,
|
timer: Instant::now(),
|
||||||
interval: Instant::now(),
|
counting: false,
|
||||||
current_frame: false,
|
current_frame: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,22 +25,26 @@ impl Default for ForceClose {
|
|||||||
|
|
||||||
impl ForceClose {
|
impl ForceClose {
|
||||||
pub fn prompt(&mut self, force_close_button_clicked: bool, sysinfo: &mut System) {
|
pub fn prompt(&mut self, force_close_button_clicked: bool, sysinfo: &mut System) {
|
||||||
if force_close_button_clicked && !self.prompting {
|
if force_close_button_clicked && !self.counting {
|
||||||
*self = Self {
|
self.button_text = "Are you sure?".to_owned();
|
||||||
button_text: "Are you sure?".to_owned(),
|
self.timer = Instant::now();
|
||||||
prompting: true,
|
self.counting = true;
|
||||||
interval: Instant::now(),
|
self.current_frame = true;
|
||||||
current_frame: true,
|
|
||||||
}
|
}
|
||||||
}
|
if self.counting && self.timer.elapsed() >= INTERVAL {
|
||||||
if self.prompting && self.interval.elapsed() <= INTERVAL {
|
self.reset();
|
||||||
if force_close_button_clicked && !self.current_frame {
|
} else if force_close_button_clicked && !self.current_frame {
|
||||||
activate(sysinfo);
|
activate(sysinfo);
|
||||||
|
self.reset();
|
||||||
|
}
|
||||||
|
self.finish_current_frame();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) {
|
||||||
*self = Self::default();
|
*self = Self::default();
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
*self = Self::default();
|
fn finish_current_frame(&mut self) {
|
||||||
}
|
|
||||||
if self.current_frame {
|
if self.current_frame {
|
||||||
self.current_frame = false;
|
self.current_frame = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
use crate::util::consts::game::{EXE_ENHANCED, EXE_LEGACY};
|
use crate::util::consts::game::{EXE_ENHANCED, EXE_LEGACY};
|
||||||
use std::path::Path;
|
use std::{
|
||||||
|
path::Path,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
use strum::{Display, EnumIter};
|
||||||
use sysinfo::System;
|
use sysinfo::System;
|
||||||
use windows::{
|
use windows::{
|
||||||
Win32::{
|
Win32::{
|
||||||
Foundation::E_INVALIDARG,
|
|
||||||
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_IN, NET_FW_RULE_DIR_OUT, NetFwPolicy2, NetFwRule,
|
||||||
@@ -13,29 +16,47 @@ use windows::{
|
|||||||
CoUninitialize,
|
CoUninitialize,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
core::{BSTR, HRESULT},
|
core::BSTR,
|
||||||
};
|
};
|
||||||
|
|
||||||
const FILTER_NAME_IN: &str = "[GTA Tools] Block all inbound traffic for GTA V";
|
const FILTER_NAME_IN: &str = "[GTA Tools] Block all inbound traffic for GTA V";
|
||||||
const FILTER_NAME_OUT: &str = "[GTA Tools] Block all outbound traffic for GTA V";
|
const FILTER_NAME_OUT: &str = "[GTA Tools] Block all outbound traffic for GTA V";
|
||||||
|
|
||||||
|
const INTERVAL: Duration = Duration::from_secs(3);
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Display, PartialEq, Eq, EnumIter)]
|
||||||
|
pub enum BlockedStatus {
|
||||||
|
Blocked,
|
||||||
|
Failed,
|
||||||
|
Unblocked,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<bool> for BlockedStatus {
|
||||||
|
fn from(value: bool) -> Self {
|
||||||
|
if value {
|
||||||
|
Self::Blocked
|
||||||
|
} else {
|
||||||
|
Self::Unblocked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct GameNetworking {
|
pub struct GameNetworking {
|
||||||
pub is_blocked: bool,
|
|
||||||
com_initialized: bool,
|
com_initialized: bool,
|
||||||
policy: INetFwPolicy2,
|
pub blocked_status: BlockedStatus,
|
||||||
|
timer: Instant,
|
||||||
|
counting: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for GameNetworking {
|
impl Default for GameNetworking {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let result = unsafe { CoInitializeEx(None, COINIT_MULTITHREADED) };
|
Self {
|
||||||
let mut gn = Self {
|
blocked_status: Self::is_blocked().into(),
|
||||||
is_blocked: false,
|
com_initialized: unsafe { CoInitializeEx(None, COINIT_MULTITHREADED) }.is_ok(),
|
||||||
com_initialized: result != HRESULT(0x80010106u32 as i32),
|
timer: Instant::now(),
|
||||||
policy: unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER).unwrap() },
|
counting: false,
|
||||||
};
|
}
|
||||||
gn.is_blocked = gn.is_blocked();
|
|
||||||
gn
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,66 +73,64 @@ impl Drop for GameNetworking {
|
|||||||
impl GameNetworking {
|
impl GameNetworking {
|
||||||
pub fn block_all(&mut self, sysinfo: &mut System) {
|
pub fn block_all(&mut self, sysinfo: &mut System) {
|
||||||
let Some(exe_path) = get_game_exe_path(sysinfo) else {
|
let Some(exe_path) = get_game_exe_path(sysinfo) else {
|
||||||
|
self.blocked_status = BlockedStatus::Failed;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let rules = unsafe { self.policy.Rules().unwrap() };
|
let policy: INetFwPolicy2 =
|
||||||
let filter_name_in = BSTR::from(FILTER_NAME_IN);
|
unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER).unwrap() };
|
||||||
let filter_name_out = BSTR::from(FILTER_NAME_OUT);
|
let rules = unsafe { policy.Rules().unwrap() };
|
||||||
unsafe {
|
|
||||||
let _ = rules.Remove(&filter_name_in);
|
|
||||||
let _ = rules.Remove(&filter_name_out);
|
|
||||||
}
|
|
||||||
let exe_path = BSTR::from(exe_path.to_string_lossy().to_string());
|
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),
|
||||||
|
] {
|
||||||
|
let _ = unsafe { rules.Remove(&BSTR::from(filter.0)) };
|
||||||
unsafe {
|
unsafe {
|
||||||
let inbound_rule: INetFwRule =
|
let rule: INetFwRule =
|
||||||
CoCreateInstance(&NetFwRule, None, CLSCTX_INPROC_SERVER).unwrap();
|
CoCreateInstance(&NetFwRule, None, CLSCTX_INPROC_SERVER).unwrap();
|
||||||
inbound_rule.SetName(&filter_name_in).unwrap();
|
rule.SetName(&BSTR::from(filter.0)).unwrap();
|
||||||
inbound_rule.SetApplicationName(&exe_path).unwrap();
|
rule.SetApplicationName(&exe_path).unwrap();
|
||||||
inbound_rule.SetDirection(NET_FW_RULE_DIR_IN).unwrap();
|
rule.SetDirection(filter.1).unwrap();
|
||||||
inbound_rule.SetEnabled(true.into()).unwrap();
|
rule.SetEnabled(true.into()).unwrap();
|
||||||
inbound_rule.SetAction(NET_FW_ACTION_BLOCK).unwrap();
|
rule.SetAction(NET_FW_ACTION_BLOCK).unwrap();
|
||||||
inbound_rule.SetProtocol(NET_FW_IP_PROTOCOL_ANY.0).unwrap();
|
rule.SetProtocol(NET_FW_IP_PROTOCOL_ANY.0).unwrap();
|
||||||
rules.Add(&inbound_rule).unwrap();
|
rules.Add(&rule).unwrap();
|
||||||
}
|
}
|
||||||
unsafe {
|
|
||||||
let outbound_rule: INetFwRule =
|
|
||||||
CoCreateInstance(&NetFwRule, None, CLSCTX_INPROC_SERVER).unwrap();
|
|
||||||
outbound_rule.SetName(&filter_name_out).unwrap();
|
|
||||||
outbound_rule.SetApplicationName(&exe_path).unwrap();
|
|
||||||
outbound_rule.SetDirection(NET_FW_RULE_DIR_OUT).unwrap();
|
|
||||||
outbound_rule.SetEnabled(true.into()).unwrap();
|
|
||||||
outbound_rule.SetAction(NET_FW_ACTION_BLOCK).unwrap();
|
|
||||||
outbound_rule.SetProtocol(NET_FW_IP_PROTOCOL_ANY.0).unwrap();
|
|
||||||
rules.Add(&outbound_rule).unwrap();
|
|
||||||
}
|
}
|
||||||
self.is_blocked = self.is_blocked();
|
self.blocked_status = Self::is_blocked().into();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unblock_all(&mut self) {
|
pub fn unblock_all(&mut self) {
|
||||||
let rules = unsafe { self.policy.Rules().unwrap() };
|
let policy: INetFwPolicy2 =
|
||||||
unsafe {
|
unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER).unwrap() };
|
||||||
let result = rules.Remove(&BSTR::from(FILTER_NAME_IN));
|
let rules = unsafe { policy.Rules().unwrap() };
|
||||||
if let Err(ref why) = result {
|
unsafe { rules.Remove(&BSTR::from(FILTER_NAME_IN)).unwrap() };
|
||||||
if why.code() != E_INVALIDARG {
|
unsafe { rules.Remove(&BSTR::from(FILTER_NAME_OUT)).unwrap() };
|
||||||
result.unwrap();
|
self.blocked_status = Self::is_blocked().into();
|
||||||
}
|
|
||||||
}
|
|
||||||
let result = rules.Remove(&BSTR::from(FILTER_NAME_OUT));
|
|
||||||
if let Err(ref why) = result {
|
|
||||||
if why.code() != E_INVALIDARG {
|
|
||||||
result.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.is_blocked = self.is_blocked();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_blocked(&self) -> bool {
|
fn is_blocked() -> bool {
|
||||||
let rules = unsafe { self.policy.Rules().unwrap() };
|
let policy: INetFwPolicy2 =
|
||||||
|
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 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() };
|
let out_rule_exists = unsafe { rules.Item(&BSTR::from(FILTER_NAME_OUT)).is_ok() };
|
||||||
in_rule_exists || out_rule_exists
|
in_rule_exists || out_rule_exists
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn if_failed_return_to_unblocked(&mut self) {
|
||||||
|
if self.blocked_status == BlockedStatus::Failed && !self.counting {
|
||||||
|
self.counting = true;
|
||||||
|
self.timer = Instant::now();
|
||||||
|
}
|
||||||
|
if self.blocked_status == BlockedStatus::Failed
|
||||||
|
&& self.counting
|
||||||
|
&& self.timer.elapsed() >= INTERVAL
|
||||||
|
{
|
||||||
|
self.counting = false;
|
||||||
|
self.blocked_status = BlockedStatus::Unblocked;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_game_exe_path(sysinfo: &mut System) -> Option<&Path> {
|
fn get_game_exe_path(sysinfo: &mut System) -> Option<&Path> {
|
||||||
|
|||||||
+24
-12
@@ -17,13 +17,23 @@ enum Stage {
|
|||||||
About,
|
About,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug)]
|
||||||
pub struct Flags {
|
pub struct Flags {
|
||||||
pub elevated: bool,
|
pub elevated: bool,
|
||||||
pub debug: bool,
|
pub debug: bool,
|
||||||
closing: bool,
|
closing: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Flags {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
elevated: win::is_elevated(),
|
||||||
|
debug: false,
|
||||||
|
closing: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct App {
|
pub struct App {
|
||||||
pub meta: Meta,
|
pub meta: Meta,
|
||||||
@@ -35,7 +45,7 @@ pub struct App {
|
|||||||
pub anti_afk: features::anti_afk::AntiAfk,
|
pub anti_afk: features::anti_afk::AntiAfk,
|
||||||
empty_session: features::empty_session::EmptySession,
|
empty_session: features::empty_session::EmptySession,
|
||||||
force_close: features::force_close::ForceClose,
|
force_close: features::force_close::ForceClose,
|
||||||
game_networking: features::game_networking::GameNetworking,
|
pub game_networking: features::game_networking::GameNetworking,
|
||||||
pub launch: features::launch::Launch,
|
pub launch: features::launch::Launch,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,9 +149,20 @@ impl App {
|
|||||||
.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| {
|
let response = ui.add_enabled_ui(self.flags.elevated, |ui| {
|
||||||
|
let label = ui.horizontal(|ui| {
|
||||||
let label = ui.label("Game's network access");
|
let label = ui.label("Game's network access");
|
||||||
|
ui.add_space(1.0);
|
||||||
|
ui.add(
|
||||||
|
egui::Image::new(egui::include_image!("../../assets/circle.svg"))
|
||||||
|
.max_size([4.0, 4.0].into())
|
||||||
|
.tint(self.game_networking.blocked_status.to_color32()),
|
||||||
|
)
|
||||||
|
.on_hover_text("This turns yellow if GTA Tools\ncannot find your game.");
|
||||||
|
self.game_networking.if_failed_return_to_unblocked();
|
||||||
|
label
|
||||||
|
});
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
let available_width = label.rect.width();
|
let available_width = label.inner.rect.width();
|
||||||
let spacing = ui.spacing().item_spacing.x;
|
let spacing = ui.spacing().item_spacing.x;
|
||||||
let button_width = (available_width - spacing) / 2.0;
|
let button_width = (available_width - spacing) / 2.0;
|
||||||
if ui
|
if ui
|
||||||
@@ -156,15 +177,6 @@ impl App {
|
|||||||
{
|
{
|
||||||
self.game_networking.unblock_all();
|
self.game_networking.unblock_all();
|
||||||
}
|
}
|
||||||
let tint = match self.game_networking.is_blocked {
|
|
||||||
true => egui::Color32::from_hex("#f96554").unwrap(),
|
|
||||||
false => egui::Color32::from_hex("#68f954").unwrap(),
|
|
||||||
};
|
|
||||||
ui.add(
|
|
||||||
egui::Image::new(egui::include_image!("../../assets/circle.svg"))
|
|
||||||
.max_size([4.0, 4.0].into())
|
|
||||||
.tint(tint),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
response.response.on_disabled_hover_text(
|
response.response.on_disabled_hover_text(
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
use crate::features::game_networking::BlockedStatus;
|
||||||
|
use eframe::egui;
|
||||||
|
|
||||||
|
pub const RED: egui::Color32 = egui::Color32::from_rgb(255, 96, 96);
|
||||||
|
pub const YELLOW: egui::Color32 = egui::Color32::from_rgb(255, 255, 96);
|
||||||
|
pub const GREEN: egui::Color32 = egui::Color32::from_rgb(96, 255, 96);
|
||||||
|
|
||||||
|
impl From<BlockedStatus> for egui::Color32 {
|
||||||
|
fn from(value: BlockedStatus) -> Self {
|
||||||
|
match value {
|
||||||
|
BlockedStatus::Blocked => RED,
|
||||||
|
BlockedStatus::Failed => YELLOW,
|
||||||
|
BlockedStatus::Unblocked => GREEN,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockedStatus {
|
||||||
|
pub fn to_color32(self) -> egui::Color32 {
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -50,6 +50,15 @@ impl App {
|
|||||||
};
|
};
|
||||||
ui.label(format!("focused: \"{current_title}\""));
|
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| {
|
||||||
|
tools::build_menu(ui, &mut self.game_networking.blocked_status);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
});
|
});
|
||||||
ui.collapsing("anti afk", |ui| {
|
ui.collapsing("anti afk", |ui| {
|
||||||
ui.label(format!(
|
ui.label(format!(
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
mod app;
|
mod app;
|
||||||
|
mod colours;
|
||||||
mod debug;
|
mod debug;
|
||||||
pub mod run;
|
pub mod run;
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
|
|||||||
+1
-3
@@ -34,11 +34,9 @@ fn app_creator(
|
|||||||
app.settings = persistent_state.settings;
|
app.settings = persistent_state.settings;
|
||||||
}
|
}
|
||||||
// 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
|
||||||
let elevated = util::win::is_elevated();
|
if !app.flags.elevated && app.settings.start_elevated {
|
||||||
if app.settings.start_elevated && !elevated {
|
|
||||||
util::win::elevate(util::win::ElevationExitMethod::Forced);
|
util::win::elevate(util::win::ElevationExitMethod::Forced);
|
||||||
}
|
}
|
||||||
app.flags.elevated = elevated;
|
|
||||||
// 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
|
||||||
|
|||||||
Reference in New Issue
Block a user