Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6cb70e87af | ||
|
|
393588d8d0 | ||
|
|
f22040d8e3 | ||
|
|
937d775f63 | ||
|
|
709b17d729 | ||
|
|
9a18ea1576 | ||
|
|
96c4fd80a4 | ||
|
|
47b647784e | ||
|
|
3cf1d708a8 | ||
|
|
e8df86c250 | ||
|
|
40bb3e2cc2 | ||
|
|
7c5e64774c | ||
|
|
a02293a518 | ||
|
|
1f296c3880 | ||
|
|
054c86e2d2 | ||
|
|
eed72cdec9
|
||
|
|
749bf9a7a7 | ||
|
|
f1f71e7533 | ||
|
|
27eeb48044 | ||
|
|
60f1868334 | ||
|
|
44e5997847
|
||
|
|
0384eee42c | ||
|
|
89a320fd0f | ||
|
|
58fd6b7e9c | ||
|
|
53fd0112c2 | ||
|
|
23fd4b12f9 | ||
|
|
e0a5821431 | ||
|
|
50b5388f8a | ||
|
|
d185ab1a57 | ||
|
|
4205a332a7 | ||
|
|
ade95b5c65 | ||
|
|
54985d350a | ||
|
|
97e1446996 | ||
|
|
746b36dfc0
|
||
|
|
200b92cd48
|
||
|
|
fb7e1f7864
|
||
|
|
b0afa9de1a
|
||
|
|
890ae36590
|
||
|
|
89eec435ff
|
||
|
|
2611dbfd3e
|
||
|
|
eafc9e1d10
|
||
|
|
0e92c9908d
|
||
|
|
8ffc5efb8d
|
||
|
|
bd8f70ed5f
|
||
|
|
9227533303
|
||
|
|
4b522d9b3f
|
||
|
|
d393bbfc37
|
||
|
|
7190dc05fe
|
||
|
|
23b6c879ae
|
||
|
|
660a478fbb
|
||
|
|
28486174f2
|
||
|
|
0f65c76055
|
||
|
|
8943e61f3a
|
||
|
|
ed0a828fd4
|
||
|
|
96ef494443
|
||
|
|
9b5340a6c5
|
||
|
|
74132ec322
|
||
|
|
a58606c91d
|
||
|
|
47adf42a48
|
||
|
|
0fb587027c
|
||
|
|
0424b7b114
|
||
|
|
e89c3acc06
|
||
|
|
ab64c29935
|
||
|
|
e216445c7f
|
||
|
|
aeb99992bb
|
||
|
|
8ab6e14560
|
||
|
|
b9ebdfdb5c
|
||
|
|
2804812691
|
||
|
|
606fb7e191
|
||
|
|
e2a779b7f5
|
||
|
|
a8b04bb72b
|
||
|
|
c34cf187d5
|
||
|
|
f07421e75a
|
||
|
|
8b4a455dc1
|
||
|
|
a1300f8d76
|
||
|
|
9e623219a5
|
||
|
|
09e19b6e16
|
||
|
|
4498c45965
|
||
|
|
af8a5b1746
|
||
|
|
560d9659f4
|
||
|
|
bf086d0442
|
||
|
|
a6ea8ad8e2
|
||
|
|
4c00d1846b
|
||
|
|
e5e335f4f3
|
||
|
|
3ab5987b67
|
||
|
|
2030796337
|
||
|
|
2edb4f492a
|
||
|
|
114292d912
|
||
|
|
8adc1f49ad
|
||
|
|
bb27334f57
|
||
|
|
1e13905885
|
||
|
|
8fc73b2992
|
||
|
|
43b72995dd
|
||
|
|
d3473d46e3
|
||
|
|
1363f5b125
|
||
|
|
d15b8070ec | ||
|
|
efccd6ed50 | ||
|
|
5c73ee7bd5 | ||
|
|
445b63fa4c | ||
|
|
941868dcc8 | ||
|
|
762a3b9b26 |
Generated
+998
-767
File diff suppressed because it is too large
Load Diff
+18
-18
@@ -1,38 +1,38 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "gta-tools"
|
name = "gta-tools"
|
||||||
version = "0.8.0"
|
version = "0.12.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
license = "Apache-2.0"
|
||||||
|
authors = ["futile <git@futile.eu>"]
|
||||||
|
description = "A toolset of convenient things for GTA V Online."
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
catppuccin-egui = { version = "5.6.0", default-features = false, features = [
|
anyhow = "1.0"
|
||||||
"egui32",
|
eframe = { version = "0.35", default-features = false, features = [
|
||||||
] }
|
|
||||||
chrono = { version = "0.4.41", default-features = false, features = ["clock"] }
|
|
||||||
eframe = { version = "0.32.2", default-features = false, features = [
|
|
||||||
"glow",
|
"glow",
|
||||||
"default_fonts",
|
"default_fonts",
|
||||||
] }
|
] }
|
||||||
egui_extras = { version = "0.32.2", default-features = false, features = [
|
egui_extras = { version = "0.35", default-features = false, features = [
|
||||||
"image",
|
"image",
|
||||||
"svg",
|
"svg",
|
||||||
] }
|
] }
|
||||||
image = { version = "0.25.8", default-features = false, features = ["png"] }
|
humantime = "2.3.0"
|
||||||
open = "5.3.2"
|
image = { version = "0.25", default-features = false, features = ["png"] }
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
log = "0.4"
|
||||||
serde_json = "1.0.143"
|
open = "5"
|
||||||
strum = { version = "0.27.2", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
windows = { version = "0.62.0", features = [
|
serde_json = "1.0"
|
||||||
|
strum = { version = "0.28", features = ["derive"] }
|
||||||
|
windows = { version = "0.62", features = [
|
||||||
"Win32_NetworkManagement_WindowsFirewall",
|
"Win32_NetworkManagement_WindowsFirewall",
|
||||||
"Win32_Security",
|
|
||||||
"Win32_System_Com",
|
"Win32_System_Com",
|
||||||
"Win32_System_Diagnostics_ToolHelp",
|
"Win32_System_Diagnostics_ToolHelp",
|
||||||
"Win32_System_Threading",
|
"Win32_System_Threading",
|
||||||
"Win32_UI_Input_KeyboardAndMouse",
|
"Win32_UI_Input_KeyboardAndMouse",
|
||||||
"Win32_UI_Shell",
|
|
||||||
"Win32_UI_WindowsAndMessaging",
|
"Win32_UI_WindowsAndMessaging",
|
||||||
] }
|
] }
|
||||||
winreg = "0.55.0"
|
winreg = "0.56"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
static_vcruntime = "2.0.0"
|
static_vcruntime = "3.0.0"
|
||||||
winresource = "0.1.23"
|
tauri-winres = "0.3"
|
||||||
|
|||||||
@@ -2,21 +2,25 @@
|
|||||||
|
|
||||||
A toolset of convenient things for GTA V Online.
|
A toolset of convenient things for GTA V Online.
|
||||||
|
|
||||||
 
|

|
||||||
|

|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> I have been forced to move this project from [GitHub](https://github.com/elituf/gta-tools) to [here](https://gitlab.com/futile/gta-tools). Sorry for the inconvenience. From now on you may always find the project source code via the redirect at [futile.eu/gta-tools](https://futile.eu/gta-tools).
|
||||||
|
|
||||||
## Installing
|
## Installing
|
||||||
|
|
||||||
**Option 1** — <ins>Download</ins>
|
**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.
|
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. GTA Tools requires administrator access.
|
||||||
|
|
||||||
It is recommended to always use an up-to-date version of GTA Tools from [releases](https://codeberg.org/futile/gta-tools/releases). You can also easily access the current latest release by going to the <kbd>About</kbd> page of GTA Tools and clicking the <kbd>↓</kbd> button near the version number.
|
|
||||||
|
|
||||||
#### Game
|
#### Game
|
||||||
|
|
||||||
@@ -24,13 +28,13 @@ This section is quite simple.
|
|||||||
|
|
||||||
There is a <kbd>Launch</kbd> feature, which will start your game on the chosen launcher and game version.
|
There is a <kbd>Launch</kbd> feature, which will start your game on the chosen launcher and game version.
|
||||||
|
|
||||||
There is also a <kbd>Force close game</kbd> feature, which simply kills all game processes. This *does not* touch Rockstar Games Launcher or any other processes, only ones named `GTA5_Enhanced.exe` or `GTA5.exe`.
|
There is also a <kbd>Force close game</kbd> feature, which simply kills all game processes. This button requires a second press after the first one for confirmation. This *does not* touch Rockstar Games Launcher or any other processes, only ones named `GTA5_Enhanced.exe` or `GTA5.exe`.
|
||||||
|
|
||||||
#### Session
|
#### Session
|
||||||
|
|
||||||
This section also has two features.
|
This section also has two features.
|
||||||
|
|
||||||
The first being <kbd>Empty current session</kbd>, which "removes everyone" from your session by suspending your game for 10 seconds, and then resuming it. This method is exceedingly similar to the Resource Monitor method, but made convenient in one button. This can be useful in numerous ways:
|
The first being <kbd>Empty current session</kbd>, which "removes everyone" from your session by blocking the game's network access for 10 seconds, then unblocking it. This can be useful in numerous ways:
|
||||||
* You are stuck loading into a session
|
* You are stuck loading into a session
|
||||||
* You are stuck loading into an interior
|
* You are stuck loading into an interior
|
||||||
* You are about to get griefed while doing a freemode mission
|
* You are about to get griefed while doing a freemode mission
|
||||||
@@ -43,10 +47,7 @@ The other feature is <kbd>Anti AFK</kbd>, which, when toggled, prevents you from
|
|||||||
|
|
||||||
#### Network
|
#### Network
|
||||||
|
|
||||||
The last section has a single feature, <kbd>Game's network access</kbd>, with two buttons, <kbd>Block</kbd> and <kbd>Unblock</kbd>, and a coloured indicator dot. This feature requires administrator, and blocks/unblocks GTA V's entire internet access using the Windows Firewall. The coloured indicator dot has these meanings:
|
The last section has a single feature, <kbd>Game's network access</kbd>, with two buttons, <kbd>Block</kbd> and <kbd>Unblock</kbd>, and a coloured indicator dot (**green** - normal, **red** - blocked). This feature blocks/unblocks GTA V's entire internet access using the Windows Firewall.
|
||||||
* **Green** - the game is connected to the internet
|
|
||||||
* **Yellow** - the game could not be found running, and thus was not blocked
|
|
||||||
* **Red** - the game is blocked from the internet
|
|
||||||
|
|
||||||
This feature is primarily useful for **replay glitching**, which is an exploit that allows you to complete any heist/mission finale, get the money for it, and then be able to do it again right away. Here's how you can do a replay glitch:
|
This feature is primarily useful for **replay glitching**, which is an exploit that allows you to complete any heist/mission finale, get the money for it, and then be able to do it again right away. Here's how you can do a replay glitch:
|
||||||
1. Get to the finale of a heist/contract/mission series, such as **The Contract: Dr. Dre**
|
1. Get to the finale of a heist/contract/mission series, such as **The Contract: Dr. Dre**
|
||||||
@@ -56,9 +57,15 @@ This feature is primarily useful for **replay glitching**, which is an exploit t
|
|||||||
5. At this point, <kbd>Unblock</kbd> the game's internet access and load back into online
|
5. At this point, <kbd>Unblock</kbd> the game's internet access and load back into online
|
||||||
6. Finally, spend any amount of money, and you should receive the finale cut, but also be able to go play the finale again immediately
|
6. Finally, spend any amount of money, and you should receive the finale cut, but also be able to go play the finale again immediately
|
||||||
|
|
||||||
## Issues
|
This feature can also be changed to <kbd>Rockstar save server access</kbd> via the **Network** dropdown of the settings. When in this mode, instead of blocking the entire game's network, it will only block the Rockstar save server at the IP `192.81.241.171`. The IP can also be changed in the same settings, if ever needed.
|
||||||
|
|
||||||
- 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.
|
Here's how you can do a replay glitch using this alternative method:
|
||||||
|
1. Get to the finale of a heist/contract/mission series, such as **The Contract: Dr. Dre**
|
||||||
|
2. At any point before you complete the finale, <kbd>Block</kbd> the save server access (for example, at the beginning of the mission)
|
||||||
|
3. Complete the finale as normal
|
||||||
|
4. After loading into online, exit to story mode
|
||||||
|
5. At this point, <kbd>Unblock</kbd> the save server access and load back into online
|
||||||
|
6. Finally, force a save by changing your outfit, and you should receive the finale cut, but also be able to go play the finale again immediately
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
-23
@@ -1,23 +1 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<svg width="512" height="512" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><circle style="fill:#fff" cx="256" cy="256" r="256"/></svg>
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
width="512"
|
|
||||||
height="512"
|
|
||||||
viewBox="0 0 512 512"
|
|
||||||
version="1.1"
|
|
||||||
id="svg1"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg">
|
|
||||||
<defs
|
|
||||||
id="defs1" />
|
|
||||||
<g
|
|
||||||
id="layer1">
|
|
||||||
<circle
|
|
||||||
style="fill:#ffffff"
|
|
||||||
id="path1"
|
|
||||||
cx="256"
|
|
||||||
cy="256"
|
|
||||||
r="256" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 458 B After Width: | Height: | Size: 147 B |
@@ -1,7 +1,34 @@
|
|||||||
fn main() {
|
fn main() {
|
||||||
static_vcruntime::metabuild();
|
static_vcruntime::metabuild();
|
||||||
winresource::WindowsResource::new()
|
tauri_winres::WindowsResource::new()
|
||||||
|
.set("FileDescription", "GTA Tools")
|
||||||
|
.set("ProductName", "GTA Tools")
|
||||||
|
.set("LegalCopyright", "futile <git@futile.eu>")
|
||||||
|
.set_language(0x0009)
|
||||||
.set_icon("assets/icon.ico")
|
.set_icon("assets/icon.ico")
|
||||||
|
.set_manifest(
|
||||||
|
r#"<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||||
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<security>
|
||||||
|
<requestedPrivileges>
|
||||||
|
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
|
||||||
|
</requestedPrivileges>
|
||||||
|
</security>
|
||||||
|
</trustInfo>
|
||||||
|
</assembly>"#,
|
||||||
|
)
|
||||||
.compile()
|
.compile()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
embed_latest_git_hash();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn embed_latest_git_hash() {
|
||||||
|
let git_rev_parse = std::process::Command::new("git")
|
||||||
|
.args(["rev-parse", "--short=8", "HEAD"])
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
let git_hash = String::from_utf8(git_rev_parse.stdout).unwrap();
|
||||||
|
println!("cargo::rustc-env=LATEST_GIT_COMMIT_HASH={git_hash}");
|
||||||
|
println!("cargo::rerun-if-changed=.git/refs/heads/main");
|
||||||
|
println!("cargo::rerun-if-changed=.git/HEAD");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
set windows-shell := ["pwsh", "-NoLogo", "-NoProfileLoadTime", "-Command"]
|
|
||||||
|
|
||||||
install:
|
|
||||||
cargo build --release
|
|
||||||
cp .\target\release\gta-tools.exe ~\.cargo\bin
|
|
||||||
cp .\target\release\gta-tools.exe ~\Documents
|
|
||||||
@@ -42,7 +42,7 @@ impl AntiAfk {
|
|||||||
fn send(vk_codes: &[VIRTUAL_KEY]) {
|
fn send(vk_codes: &[VIRTUAL_KEY]) {
|
||||||
let mut inputs = Vec::new();
|
let mut inputs = Vec::new();
|
||||||
for &vk_code in vk_codes {
|
for &vk_code in vk_codes {
|
||||||
let scan_code = unsafe { MapVirtualKeyW(u32::from(vk_code.0), MAPVK_VK_TO_VSC) as u16 };
|
let scan_code = unsafe { MapVirtualKeyW(u32::from(vk_code.0), MAPVK_VK_TO_VSC) } as u16;
|
||||||
for event in [KEYBD_EVENT_FLAGS(0), KEYEVENTF_KEYUP] {
|
for event in [KEYBD_EVENT_FLAGS(0), KEYEVENTF_KEYUP] {
|
||||||
let mut input = INPUT {
|
let mut input = INPUT {
|
||||||
r#type: INPUT_KEYBOARD,
|
r#type: INPUT_KEYBOARD,
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
use crate::util::{
|
use crate::util::{
|
||||||
consts::game::{EXE_ENHANCED, EXE_LEGACY},
|
|
||||||
countdown::Countdown,
|
countdown::Countdown,
|
||||||
log,
|
firewall::{Firewall, RuleDirection, RuleMode, RuleProtocol},
|
||||||
system_info::SystemInfo,
|
system_info::SystemInfo,
|
||||||
};
|
};
|
||||||
|
use anyhow::Result;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use windows::Win32::{
|
|
||||||
Foundation::{CloseHandle, HANDLE, NTSTATUS},
|
const FILTER_NAME_EMPTY_SESSION_IN: &str = "[GTA Tools] Block inbound UDP traffic for all of GTA V";
|
||||||
System::Threading::{OpenProcess, PROCESS_SUSPEND_RESUME},
|
const FILTER_NAME_EMPTY_SESSION_OUT: &str =
|
||||||
};
|
"[GTA Tools] Block outbound UDP traffic for all of GTA V";
|
||||||
|
|
||||||
const INTERVAL: Duration = Duration::from_secs(10);
|
const INTERVAL: Duration = Duration::from_secs(10);
|
||||||
|
|
||||||
@@ -30,57 +30,42 @@ impl Default for EmptySession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EmptySession {
|
impl EmptySession {
|
||||||
pub fn run_timers(&mut self, game_handle: &mut HANDLE) {
|
pub fn run_timers(&mut self, firewall: &Firewall) -> Result<()> {
|
||||||
if self.disabled {
|
if self.disabled {
|
||||||
self.countdown.count();
|
self.countdown.count();
|
||||||
} else {
|
} else {
|
||||||
self.countdown.reset();
|
self.countdown.reset();
|
||||||
}
|
}
|
||||||
if self.interval.elapsed() >= INTERVAL {
|
if self.interval.elapsed() >= INTERVAL {
|
||||||
deactivate(game_handle);
|
deactivate(firewall)?;
|
||||||
self.disabled = false;
|
self.disabled = false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[link(name = "ntdll")]
|
|
||||||
unsafe extern "system" {
|
|
||||||
unsafe fn NtSuspendProcess(ProcessHandle: HANDLE) -> NTSTATUS;
|
|
||||||
unsafe fn NtResumeProcess(ProcessHandle: HANDLE) -> NTSTATUS;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_gta_pid(system_info: &mut SystemInfo) -> Option<u32> {
|
|
||||||
system_info.refresh();
|
|
||||||
system_info
|
|
||||||
.processes()
|
|
||||||
.iter()
|
|
||||||
.find(|p| p.name() == EXE_ENHANCED || p.name() == EXE_LEGACY)
|
|
||||||
.map(|p| p.pid())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn activate(game_handle: &mut HANDLE, system_info: &mut SystemInfo) -> Result<(), ()> {
|
|
||||||
let Some(pid) = get_gta_pid(system_info) else {
|
|
||||||
return Err(());
|
|
||||||
};
|
|
||||||
unsafe {
|
|
||||||
match OpenProcess(PROCESS_SUSPEND_RESUME, false, pid) {
|
|
||||||
Ok(handle) => *game_handle = handle,
|
|
||||||
Err(why) => {
|
|
||||||
let message = format!("failed to suspend game for empty session:\n{why}");
|
|
||||||
log::log(log::LogLevel::Error, &message);
|
|
||||||
return Err(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let _ = NtSuspendProcess(*game_handle);
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn deactivate(game_handle: &mut HANDLE) {
|
pub fn activate(system_info: &mut SystemInfo, firewall: &Firewall) -> Result<bool> {
|
||||||
unsafe {
|
let Some(exe_path) = system_info.get_game_exe_path() else {
|
||||||
if !game_handle.is_invalid() {
|
log::info!("wasn't able to find game exe");
|
||||||
let _ = NtResumeProcess(*game_handle);
|
return Ok(false);
|
||||||
let _ = CloseHandle(*game_handle);
|
};
|
||||||
}
|
firewall.add(
|
||||||
|
FILTER_NAME_EMPTY_SESSION_IN,
|
||||||
|
RuleMode::Executable(exe_path.to_path_buf()),
|
||||||
|
RuleDirection::In,
|
||||||
|
RuleProtocol::Udp,
|
||||||
|
)?;
|
||||||
|
firewall.add(
|
||||||
|
FILTER_NAME_EMPTY_SESSION_OUT,
|
||||||
|
RuleMode::Executable(exe_path.to_path_buf()),
|
||||||
|
RuleDirection::Out,
|
||||||
|
RuleProtocol::Udp,
|
||||||
|
)?;
|
||||||
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn deactivate(firewall: &Firewall) -> Result<()> {
|
||||||
|
firewall.remove(FILTER_NAME_EMPTY_SESSION_IN)?;
|
||||||
|
firewall.remove(FILTER_NAME_EMPTY_SESSION_OUT)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use crate::util::{
|
use crate::util::{
|
||||||
consts::game::{EXE_ENHANCED, EXE_LEGACY},
|
consts::game::{EXE_ENHANCED, EXE_LEGACY},
|
||||||
log,
|
|
||||||
system_info::SystemInfo,
|
system_info::SystemInfo,
|
||||||
};
|
};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
@@ -62,10 +61,7 @@ fn activate(system_info: &mut SystemInfo) {
|
|||||||
.filter(|p| p.name() == EXE_ENHANCED || p.name() == EXE_LEGACY)
|
.filter(|p| p.name() == EXE_ENHANCED || p.name() == EXE_LEGACY)
|
||||||
.for_each(|p| {
|
.for_each(|p| {
|
||||||
if !p.kill() {
|
if !p.kill() {
|
||||||
log::log(
|
log::error!("failed to force close game, probably due to access denied");
|
||||||
log::LogLevel::Error,
|
|
||||||
"failed to force close game, probably due to access denied",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
system_info.refresh();
|
system_info.refresh();
|
||||||
|
|||||||
+69
-114
@@ -1,145 +1,100 @@
|
|||||||
use crate::util::{
|
use crate::{
|
||||||
consts::game::{EXE_ENHANCED, EXE_LEGACY},
|
gui::settings::BlockMethod,
|
||||||
|
util::{
|
||||||
|
firewall::{Firewall, RuleDirection, RuleMode, RuleProtocol},
|
||||||
system_info::SystemInfo,
|
system_info::SystemInfo,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use std::{
|
use anyhow::Result;
|
||||||
path::Path,
|
|
||||||
time::{Duration, Instant},
|
|
||||||
};
|
|
||||||
use strum::{Display, EnumIter};
|
use strum::{Display, EnumIter};
|
||||||
use windows::{
|
|
||||||
Win32::{
|
|
||||||
NetworkManagement::WindowsFirewall::{
|
|
||||||
INetFwPolicy2, INetFwRule, NET_FW_ACTION_BLOCK, NET_FW_IP_PROTOCOL_ANY,
|
|
||||||
NET_FW_RULE_DIR_IN, NET_FW_RULE_DIR_OUT, NetFwPolicy2, NetFwRule,
|
|
||||||
},
|
|
||||||
System::Com::{
|
|
||||||
CLSCTX_INPROC_SERVER, COINIT_MULTITHREADED, CoCreateInstance, CoInitializeEx,
|
|
||||||
CoUninitialize,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
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);
|
#[derive(Clone, Copy, Debug, Default, Display, EnumIter, PartialEq)]
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Display, PartialEq, Eq, EnumIter)]
|
|
||||||
pub enum BlockedStatus {
|
pub enum BlockedStatus {
|
||||||
Blocked,
|
#[default]
|
||||||
Failed,
|
|
||||||
Unblocked,
|
Unblocked,
|
||||||
}
|
Server,
|
||||||
|
Executable,
|
||||||
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 {
|
||||||
com_initialized: bool,
|
pub blocked: BlockedStatus,
|
||||||
pub blocked_status: BlockedStatus,
|
|
||||||
timer: Instant,
|
|
||||||
counting: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for GameNetworking {
|
impl Default for GameNetworking {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
let firewall = Firewall::default();
|
||||||
Self {
|
Self {
|
||||||
blocked_status: Self::is_blocked().into(),
|
blocked: if firewall.is_blocked(FILTER_NAME_SAVE_SERVER).unwrap() {
|
||||||
com_initialized: unsafe { CoInitializeEx(None, COINIT_MULTITHREADED) }.is_ok(),
|
BlockedStatus::Server
|
||||||
timer: Instant::now(),
|
} else if firewall.is_blocked(FILTER_NAME_EXE).unwrap() {
|
||||||
counting: false,
|
BlockedStatus::Executable
|
||||||
}
|
} else {
|
||||||
}
|
BlockedStatus::Unblocked
|
||||||
}
|
},
|
||||||
|
|
||||||
impl Drop for GameNetworking {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
if self.com_initialized {
|
|
||||||
CoUninitialize();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GameNetworking {
|
impl GameNetworking {
|
||||||
pub fn block_all(&mut self, system_info: &mut SystemInfo) {
|
pub fn block_exe(&mut self, system_info: &mut SystemInfo, firewall: &Firewall) -> Result<()> {
|
||||||
let Some(exe_path) = get_game_exe_path(system_info) else {
|
let Some(exe_path) = system_info.get_game_exe_path() else {
|
||||||
self.blocked_status = BlockedStatus::Failed;
|
log::warn!("Unable to find game executable path.");
|
||||||
return;
|
return Ok(());
|
||||||
};
|
};
|
||||||
let policy: INetFwPolicy2 =
|
firewall
|
||||||
unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER).unwrap() };
|
.add(
|
||||||
let rules = unsafe { policy.Rules().unwrap() };
|
FILTER_NAME_EXE,
|
||||||
let exe_path = BSTR::from(exe_path.to_string_lossy().to_string());
|
RuleMode::Executable(exe_path.to_path_buf()),
|
||||||
for filter in [
|
RuleDirection::Out,
|
||||||
(FILTER_NAME_IN, NET_FW_RULE_DIR_IN),
|
RuleProtocol::Any,
|
||||||
(FILTER_NAME_OUT, NET_FW_RULE_DIR_OUT),
|
)
|
||||||
] {
|
.inspect(|_| self.blocked = BlockedStatus::Executable)
|
||||||
let _ = unsafe { rules.Remove(&BSTR::from(filter.0)) };
|
|
||||||
unsafe {
|
|
||||||
let rule: INetFwRule =
|
|
||||||
CoCreateInstance(&NetFwRule, None, CLSCTX_INPROC_SERVER).unwrap();
|
|
||||||
rule.SetName(&BSTR::from(filter.0)).unwrap();
|
|
||||||
rule.SetApplicationName(&exe_path).unwrap();
|
|
||||||
rule.SetDirection(filter.1).unwrap();
|
|
||||||
rule.SetEnabled(true.into()).unwrap();
|
|
||||||
rule.SetAction(NET_FW_ACTION_BLOCK).unwrap();
|
|
||||||
rule.SetProtocol(NET_FW_IP_PROTOCOL_ANY.0).unwrap();
|
|
||||||
rules.Add(&rule).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.blocked_status = Self::is_blocked().into();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unblock_all(&mut self) {
|
pub fn unblock_exe(&mut self, firewall: &Firewall) -> Result<()> {
|
||||||
let policy: INetFwPolicy2 =
|
firewall
|
||||||
unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER).unwrap() };
|
.remove(FILTER_NAME_EXE)
|
||||||
let rules = unsafe { policy.Rules().unwrap() };
|
.inspect(|_| self.blocked = BlockedStatus::Unblocked)
|
||||||
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 {
|
pub fn block_save_server(&mut self, save_server_ip: &str, firewall: &Firewall) -> Result<()> {
|
||||||
let policy: INetFwPolicy2 =
|
firewall
|
||||||
unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER).unwrap() };
|
.add(
|
||||||
let rules = unsafe { policy.Rules().unwrap() };
|
FILTER_NAME_SAVE_SERVER,
|
||||||
let in_rule_exists = unsafe { rules.Item(&BSTR::from(FILTER_NAME_IN)).is_ok() };
|
RuleMode::Address(save_server_ip.to_owned()),
|
||||||
let out_rule_exists = unsafe { rules.Item(&BSTR::from(FILTER_NAME_OUT)).is_ok() };
|
RuleDirection::Out,
|
||||||
in_rule_exists || out_rule_exists
|
RuleProtocol::Any,
|
||||||
|
)
|
||||||
|
.inspect(|_| self.blocked = BlockedStatus::Server)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn if_failed_return_to_boolean(&mut self) {
|
pub fn unblock_save_server(&mut self, firewall: &Firewall) -> Result<()> {
|
||||||
if self.blocked_status == BlockedStatus::Failed && !self.counting {
|
firewall
|
||||||
self.counting = true;
|
.remove(FILTER_NAME_SAVE_SERVER)
|
||||||
self.timer = Instant::now();
|
.inspect(|_| self.blocked = BlockedStatus::Unblocked)
|
||||||
}
|
|
||||||
if self.blocked_status == BlockedStatus::Failed
|
|
||||||
&& self.counting
|
|
||||||
&& self.timer.elapsed() >= INTERVAL
|
|
||||||
{
|
|
||||||
self.counting = false;
|
|
||||||
self.blocked_status = Self::is_blocked().into();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_game_exe_path(system_info: &mut SystemInfo) -> Option<&Path> {
|
pub fn ensure_block_exclusivity(
|
||||||
system_info.refresh();
|
&mut self,
|
||||||
system_info
|
block_method: BlockMethod,
|
||||||
.processes()
|
firewall: &Firewall,
|
||||||
.iter()
|
) -> Result<()> {
|
||||||
.find(|p| p.name() == EXE_ENHANCED || p.name() == EXE_LEGACY)
|
match block_method {
|
||||||
.and_then(|p| p.exe())
|
BlockMethod::EntireGame => {
|
||||||
|
if self.blocked == BlockedStatus::Server {
|
||||||
|
self.unblock_save_server(firewall)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BlockMethod::SaveServer => {
|
||||||
|
if self.blocked == BlockedStatus::Executable {
|
||||||
|
self.unblock_exe(firewall)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-17
@@ -22,39 +22,42 @@ pub struct Launch {
|
|||||||
pub fn launch(platform: &Platform, version: &LaunchVersion) {
|
pub fn launch(platform: &Platform, version: &LaunchVersion) {
|
||||||
match platform {
|
match platform {
|
||||||
Platform::Steam => {
|
Platform::Steam => {
|
||||||
let steam_url = if *version == LaunchVersion::Enhanced {
|
let steam_url = match version {
|
||||||
"steam://run/3240220"
|
LaunchVersion::Enhanced => "steam://run/3240220",
|
||||||
} else {
|
LaunchVersion::Legacy => "steam://run/271590",
|
||||||
"steam://run/271590"
|
|
||||||
};
|
};
|
||||||
let _ = open::that_detached(steam_url);
|
open::that_detached(steam_url).unwrap();
|
||||||
}
|
}
|
||||||
Platform::Rockstar => {
|
Platform::Rockstar => {
|
||||||
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
|
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
|
||||||
let rockstar_url = if *version == LaunchVersion::Enhanced {
|
let rockstar_url = match version {
|
||||||
r"SOFTWARE\WOW6432Node\Rockstar Games\GTAV Enhanced"
|
LaunchVersion::Enhanced => r"SOFTWARE\WOW6432Node\Rockstar Games\GTAV Enhanced",
|
||||||
} else {
|
LaunchVersion::Legacy => r"SOFTWARE\WOW6432Node\Rockstar Games\Grand Theft Auto V",
|
||||||
r"SOFTWARE\WOW6432Node\Rockstar Games\Grand Theft Auto V"
|
|
||||||
};
|
};
|
||||||
let Ok(gta_v_enhanced) = hklm.open_subkey(rockstar_url) else {
|
let Ok(gta_key) = hklm.open_subkey(rockstar_url) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Ok(install_folder): Result<String, std::io::Error> =
|
let Ok(install_folder): Result<String, std::io::Error> =
|
||||||
gta_v_enhanced.get_value("InstallFolder")
|
gta_key.get_value("InstallFolder")
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let mut play_gtav_path = PathBuf::from(install_folder);
|
let mut exe_path = PathBuf::from(install_folder);
|
||||||
play_gtav_path.push("PlayGTAV.exe");
|
exe_path.push("PlayGTAV.exe");
|
||||||
let _ = Command::new(play_gtav_path).spawn();
|
if let Err(why) = Command::new(exe_path).spawn() {
|
||||||
|
log::warn!("Couldn't launch GTA 5 via Rockstar Games Launcher: {why}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Platform::Epic => {
|
Platform::Epic => {
|
||||||
let epic_url = if *version == LaunchVersion::Enhanced {
|
let epic_url = match version {
|
||||||
|
LaunchVersion::Enhanced => {
|
||||||
"com.epicgames.launcher://apps/8769e24080ea413b8ebca3f1b8c50951?action=launch&silent=true"
|
"com.epicgames.launcher://apps/8769e24080ea413b8ebca3f1b8c50951?action=launch&silent=true"
|
||||||
} else {
|
}
|
||||||
|
LaunchVersion::Legacy => {
|
||||||
"com.epicgames.launcher://apps/9d2d0eb64d5c44529cece33fe2a46482?action=launch&silent=true"
|
"com.epicgames.launcher://apps/9d2d0eb64d5c44529cece33fe2a46482?action=launch&silent=true"
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let _ = open::that_detached(epic_url);
|
open::that_detached(epic_url).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-2
@@ -1,5 +1,4 @@
|
|||||||
mod app;
|
pub mod app;
|
||||||
mod colours;
|
|
||||||
mod debug;
|
mod debug;
|
||||||
pub mod run;
|
pub mod run;
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
|
|||||||
+166
-97
@@ -1,8 +1,16 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
features,
|
features::{self, game_networking::BlockedStatus},
|
||||||
gui::{colours, settings::Settings, tools, ui_ext::UiExt},
|
gui::{
|
||||||
|
settings::{BlockMethod, ROCKSTAR_SAVE_SERVER, Settings},
|
||||||
|
tools,
|
||||||
|
ui_ext::UiExt,
|
||||||
|
},
|
||||||
util::{
|
util::{
|
||||||
consts::game::WINDOW_TITLE, persistent_state::PersistentState, system_info::SystemInfo, win,
|
consts::{colours, game::WINDOW_TITLE, path},
|
||||||
|
firewall::Firewall,
|
||||||
|
persistent_state::PersistentState,
|
||||||
|
system_info::SystemInfo,
|
||||||
|
win,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
@@ -21,7 +29,6 @@ enum Stage {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Flags {
|
pub struct Flags {
|
||||||
pub elevated: bool,
|
|
||||||
pub debug: bool,
|
pub debug: bool,
|
||||||
closing: bool,
|
closing: bool,
|
||||||
}
|
}
|
||||||
@@ -29,7 +36,6 @@ pub struct Flags {
|
|||||||
impl Default for Flags {
|
impl Default for Flags {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
elevated: win::is_elevated(),
|
|
||||||
debug: false,
|
debug: false,
|
||||||
closing: false,
|
closing: false,
|
||||||
}
|
}
|
||||||
@@ -42,7 +48,7 @@ pub struct App {
|
|||||||
stage: Stage,
|
stage: Stage,
|
||||||
pub flags: Flags,
|
pub flags: Flags,
|
||||||
pub system_info: SystemInfo,
|
pub system_info: SystemInfo,
|
||||||
game_handle: windows::Win32::Foundation::HANDLE,
|
firewall: Firewall,
|
||||||
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,
|
||||||
@@ -51,48 +57,39 @@ pub struct App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl eframe::App for App {
|
impl eframe::App for App {
|
||||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
|
||||||
ctx.request_repaint_after(Duration::from_millis(100));
|
ui.request_repaint_after(Duration::from_millis(100));
|
||||||
self.empty_session.run_timers(&mut self.game_handle);
|
self.empty_session.run_timers(&self.firewall).unwrap();
|
||||||
egui::TopBottomPanel::bottom("bottom_panel")
|
egui::Panel::bottom("bottom_panel")
|
||||||
.exact_height(25.0)
|
.exact_size(25.0)
|
||||||
.show(ctx, |ui| {
|
.show(ui, |ui| {
|
||||||
ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| {
|
ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| {
|
||||||
ui.build_menu(&mut self.stage);
|
ui.build_menu(&mut self.stage);
|
||||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
|
||||||
let button = ui
|
|
||||||
.add_enabled(!self.flags.elevated, egui::Button::new("Elevate"))
|
|
||||||
.on_hover_text("Relaunch ourselves as administrator.")
|
|
||||||
.on_disabled_hover_text("We are already running elevated.");
|
|
||||||
if button.clicked() {
|
|
||||||
win::elevate(win::ElevationExitMethod::Gentle(&mut self.flags.closing));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
egui::CentralPanel::default().show(ui, |ui| {
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
|
||||||
egui::ScrollArea::vertical()
|
egui::ScrollArea::vertical()
|
||||||
.auto_shrink([false, true])
|
.auto_shrink([false, true])
|
||||||
.show(ui, |ui| match self.stage {
|
.show(ui, |ui| match self.stage {
|
||||||
Stage::Main => self.show_main_stage(ctx, ui),
|
Stage::Main => self.show_main_stage(ui),
|
||||||
Stage::Settings => self.show_settings_stage(ctx, ui),
|
Stage::Settings => self.show_settings_stage(ui),
|
||||||
Stage::About => self.show_about_stage(ctx, ui),
|
Stage::About => self.show_about_stage(ui),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
if tools::debug_keycombo_pressed(ctx) || tools::debug_viewport_close_pressed(ctx) {
|
if tools::debug_keycombo_pressed(ui) || tools::debug_viewport_close_pressed(ui) {
|
||||||
self.flags.debug = !self.flags.debug;
|
self.flags.debug = !self.flags.debug;
|
||||||
}
|
}
|
||||||
if self.flags.debug {
|
if self.flags.debug {
|
||||||
self.show_debug_viewport(ctx);
|
self.show_debug_viewport(ui);
|
||||||
}
|
}
|
||||||
if self.flags.closing {
|
if self.flags.closing {
|
||||||
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
ui.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
fn show_game_section(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) {
|
fn show_game_section(&mut self, ui: &mut egui::Ui) {
|
||||||
ui.header("Game");
|
ui.header("Game");
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
if ui.button("Launch").clicked() {
|
if ui.button("Launch").clicked() {
|
||||||
@@ -105,24 +102,18 @@ impl App {
|
|||||||
ui.build_menu(&mut self.launch.selected);
|
ui.build_menu(&mut self.launch.selected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
let force_close_button = ui.add_sized(
|
let force_close_button = ui.button(&self.force_close.button_text);
|
||||||
[104.0, 0.0],
|
|
||||||
egui::Button::new(&self.force_close.button_text),
|
|
||||||
);
|
|
||||||
self.force_close
|
self.force_close
|
||||||
.prompt(force_close_button.clicked(), &mut self.system_info);
|
.prompt(force_close_button.clicked(), &mut self.system_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_session_section(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) {
|
fn show_session_section(&mut self, ui: &mut egui::Ui) {
|
||||||
ui.header("Session");
|
ui.header("Session");
|
||||||
ui.add_enabled_ui(!self.empty_session.disabled, |ui| {
|
ui.add_enabled_ui(!self.empty_session.disabled, |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
if ui.button("Empty current session").clicked()
|
if ui.button("Empty current session").clicked()
|
||||||
&& features::empty_session::activate(
|
&& features::empty_session::activate(&mut self.system_info, &self.firewall)
|
||||||
&mut self.game_handle,
|
.unwrap()
|
||||||
&mut self.system_info,
|
|
||||||
)
|
|
||||||
.is_ok()
|
|
||||||
{
|
{
|
||||||
self.empty_session.interval = Instant::now();
|
self.empty_session.interval = Instant::now();
|
||||||
self.empty_session.disabled = true;
|
self.empty_session.disabled = true;
|
||||||
@@ -137,9 +128,9 @@ impl App {
|
|||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
ui.add_enabled_ui(false, |ui| {
|
ui.add_enabled_ui(false, |ui| {
|
||||||
ui.label(if win::is_window_focused(WINDOW_TITLE) {
|
ui.label(if win::is_window_focused(WINDOW_TITLE) {
|
||||||
"GTA is focused."
|
"Game is focused."
|
||||||
} else {
|
} else {
|
||||||
"GTA is not focused!"
|
"Game is not focused!"
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -149,104 +140,179 @@ impl App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_network_section(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) {
|
fn show_network_section(&mut self, ui: &mut egui::Ui) {
|
||||||
ui.header("Network");
|
ui.header("Network");
|
||||||
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| {
|
|
||||||
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(match self.game_networking.blocked {
|
||||||
.on_hover_text(
|
BlockedStatus::Executable
|
||||||
"This turns yellow if GTA Tools\ncannot find your game.",
|
if self.settings.block_method == BlockMethod::EntireGame =>
|
||||||
);
|
{
|
||||||
self.game_networking.if_failed_return_to_boolean();
|
colours::RED
|
||||||
|
}
|
||||||
|
BlockedStatus::Server
|
||||||
|
if self.settings.block_method == BlockMethod::SaveServer =>
|
||||||
|
{
|
||||||
|
colours::RED
|
||||||
|
}
|
||||||
|
_ => colours::GREEN,
|
||||||
|
});
|
||||||
label
|
label
|
||||||
});
|
});
|
||||||
ui.horizontal(|ui| {
|
ui.allocate_ui_with_layout(
|
||||||
let available_width = label.inner.rect.width();
|
egui::vec2(label.inner.rect.width(), 0.0),
|
||||||
let spacing = ui.spacing().item_spacing.x;
|
egui::Layout::top_down(egui::Align::Min),
|
||||||
let button_width = (available_width - spacing) / 2.0;
|
|ui| {
|
||||||
if ui
|
ui.columns(2, |columns| {
|
||||||
.add_sized([button_width, 18.0], egui::Button::new("Block"))
|
columns[0].vertical_centered_justified(|ui| {
|
||||||
.clicked()
|
if ui.button("Block").clicked() {
|
||||||
{
|
match self.settings.block_method {
|
||||||
self.game_networking.block_all(&mut self.system_info);
|
BlockMethod::EntireGame => {
|
||||||
|
self.game_networking
|
||||||
|
.block_exe(&mut self.system_info, &self.firewall)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
BlockMethod::SaveServer => {
|
||||||
|
self.game_networking
|
||||||
|
.block_save_server(
|
||||||
|
&self.settings.save_server_ip,
|
||||||
|
&self.firewall,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
columns[1].vertical_centered_justified(|ui| {
|
||||||
|
if ui.button("Unblock").clicked() {
|
||||||
|
match self.settings.block_method {
|
||||||
|
BlockMethod::EntireGame => {
|
||||||
|
self.game_networking
|
||||||
|
.unblock_exe(&self.firewall)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
BlockMethod::SaveServer => {
|
||||||
|
self.game_networking
|
||||||
|
.unblock_save_server(&self.firewall)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ui
|
|
||||||
.add_sized([button_width, 18.0], egui::Button::new("Unblock"))
|
|
||||||
.clicked()
|
|
||||||
{
|
|
||||||
self.game_networking.unblock_all();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
response.response.on_disabled_hover_text(
|
},
|
||||||
"This requires administrator.\nUse the Elevate button.",
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_main_stage(&mut self, ctx: &egui::Context, ui: &mut egui::Ui) {
|
fn show_main_stage(&mut self, ui: &mut egui::Ui) {
|
||||||
self.show_game_section(ctx, ui);
|
self.show_game_section(ui);
|
||||||
self.show_session_section(ctx, ui);
|
self.show_session_section(ui);
|
||||||
self.show_network_section(ctx, ui);
|
self.show_network_section(ui);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_settings_stage(&mut self, ctx: &egui::Context, ui: &mut egui::Ui) {
|
fn show_settings_stage(&mut self, ui: &mut egui::Ui) {
|
||||||
ui.horizontal(|ui| {
|
ui.collapsing("General", |ui| {
|
||||||
ui.label("Launch version");
|
|
||||||
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| {
|
||||||
ui.build_menu(&mut self.settings.theme);
|
ui.build_menu(&mut self.settings.theme);
|
||||||
});
|
});
|
||||||
if selection != self.settings.theme {
|
if selection != self.settings.theme {
|
||||||
catppuccin_egui::set_theme(ctx, self.settings.theme.into());
|
ui.set_theme(egui::Theme::from(self.settings.theme));
|
||||||
|
}
|
||||||
|
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.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");
|
||||||
|
if let Err(why) = self
|
||||||
|
.game_networking
|
||||||
|
.ensure_block_exclusivity(self.settings.block_method, &self.firewall)
|
||||||
|
{
|
||||||
|
log::warn!("Couldn't ensure block exclusivity: {why}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if self.settings.block_method == BlockMethod::SaveServer {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.add(
|
||||||
|
egui::TextEdit::singleline(&mut self.settings.save_server_ip)
|
||||||
|
.char_limit(15)
|
||||||
|
.desired_width(100.0),
|
||||||
|
);
|
||||||
|
ui.label("Save server IP");
|
||||||
|
if self.settings.save_server_ip != Settings::default().save_server_ip {
|
||||||
|
if ui.button("↺").clicked() {
|
||||||
|
self.settings.save_server_ip = String::from(ROCKSTAR_SAVE_SERVER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
ui.collapsing("Miscellaneous", |ui| {
|
||||||
|
if ui.button("Open storage path").clicked() {
|
||||||
|
open::that_detached(path::APP_STORAGE.as_path()).unwrap();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
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, ui: &mut egui::Ui) {
|
||||||
ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| {
|
ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.spacing_mut().item_spacing.x = 0.0;
|
ui.spacing_mut().item_spacing.x = 0.0;
|
||||||
ui.label("with ");
|
ui.label("with ");
|
||||||
ui.scope(|ui| {
|
ui.scope(|ui| {
|
||||||
ui.style_mut().visuals.hyperlink_color = colours::RED;
|
ui.style_mut().visuals.override_text_color = Some(colours::RED);
|
||||||
ui.hyperlink_to("❤", "https://codeberg.org/futile/gta-tools");
|
ui.label("❤");
|
||||||
});
|
});
|
||||||
ui.label(" from ");
|
ui.label(" from ");
|
||||||
|
ui.scope(|ui| {
|
||||||
|
ui.style_mut().visuals.hyperlink_color =
|
||||||
|
egui::Theme::from(self.settings.theme)
|
||||||
|
.default_style()
|
||||||
|
.visuals
|
||||||
|
.text_color();
|
||||||
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);
|
||||||
|
if ui.button("").on_hover_text("View source code").clicked() {
|
||||||
|
open::that("https://futile.eu/gta-tools").unwrap();
|
||||||
|
}
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
ui.label("(dev)");
|
ui.label("(dev)");
|
||||||
}
|
}
|
||||||
ui.label(format!("v{}", env!("CARGO_PKG_VERSION")));
|
ui.label(format!("v{}", env!("CARGO_PKG_VERSION")))
|
||||||
ui.scope(|ui| {
|
.on_hover_text(
|
||||||
ui.style_mut().spacing.button_padding = egui::Vec2::new(3.0, 0.0);
|
egui::RichText::new(env!("LATEST_GIT_COMMIT_HASH")).monospace(),
|
||||||
let button = ui
|
);
|
||||||
.button("⬇")
|
|
||||||
.on_hover_text("Go to current latest version.");
|
|
||||||
if button.clicked() {
|
|
||||||
let _ =
|
|
||||||
open::that("https://codeberg.org/futile/gta-tools/releases/latest");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
ui.add(egui::Image::new(egui::include_image!(
|
ui.add(egui::Image::new(egui::include_image!(
|
||||||
@@ -261,10 +327,13 @@ impl Drop for App {
|
|||||||
// save any persistent state to config file
|
// save any persistent state to config file
|
||||||
PersistentState {
|
PersistentState {
|
||||||
launcher: self.launch.selected,
|
launcher: self.launch.selected,
|
||||||
|
anti_afk_enabled: self.anti_afk.enabled,
|
||||||
settings: self.settings.clone(),
|
settings: self.settings.clone(),
|
||||||
}
|
}
|
||||||
.set();
|
.set();
|
||||||
// make sure we are not suspending game
|
// make sure we are not network blocking game
|
||||||
features::empty_session::deactivate(&mut self.game_handle);
|
if let Err(why) = features::empty_session::deactivate(&self.firewall) {
|
||||||
|
log::error!("couldn't deactivate empty session: {why}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+12
-17
@@ -4,41 +4,36 @@ use crate::{
|
|||||||
tools,
|
tools,
|
||||||
ui_ext::UiExt,
|
ui_ext::UiExt,
|
||||||
},
|
},
|
||||||
util::consts::{
|
util::consts::game::{EXE_ENHANCED, EXE_LEGACY},
|
||||||
game::{EXE_ENHANCED, EXE_LEGACY},
|
|
||||||
path,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
fn add_debug_viewport_contents(&mut self, ui: &mut egui::Ui) {
|
fn add_debug_viewport_contents(&mut self, ui: &mut egui::Ui) {
|
||||||
ui.collapsing("misc", |ui| {
|
ui.collapsing("misc", |ui| {
|
||||||
if ui.button("open storage path").clicked() {
|
|
||||||
open::that_detached(path::APP_STORAGE.as_path()).unwrap();
|
|
||||||
}
|
|
||||||
ui.scope(|ui| {
|
ui.scope(|ui| {
|
||||||
use windows::Win32::UI::WindowsAndMessaging::{
|
use windows::Win32::UI::WindowsAndMessaging::{
|
||||||
GetForegroundWindow, GetWindowTextW,
|
GetForegroundWindow, GetWindowTextW,
|
||||||
};
|
};
|
||||||
|
let current_title = {
|
||||||
let mut buffer = [0; 512];
|
let mut buffer = [0; 512];
|
||||||
let current_title = unsafe {
|
let hwnd = unsafe { GetForegroundWindow() };
|
||||||
let hwnd = GetForegroundWindow();
|
let length = unsafe { GetWindowTextW(hwnd, &mut buffer) };
|
||||||
let length = GetWindowTextW(hwnd, &mut buffer);
|
|
||||||
String::from_utf16_lossy(&buffer[..length as usize])
|
String::from_utf16_lossy(&buffer[..length as usize])
|
||||||
};
|
};
|
||||||
ui.label(format!("focused: \"{current_title}\""));
|
ui.label(format!("focused: \"{current_title}\""));
|
||||||
});
|
});
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("blocked_status");
|
ui.label("blocked");
|
||||||
egui::ComboBox::from_id_salt("blocked_status")
|
egui::ComboBox::from_id_salt("blocked")
|
||||||
.selected_text(self.game_networking.blocked_status.to_string())
|
.selected_text(self.game_networking.blocked.to_string())
|
||||||
.show_ui(ui, |ui| {
|
.show_ui(ui, |ui| ui.build_menu(&mut self.game_networking.blocked));
|
||||||
ui.build_menu(&mut self.game_networking.blocked_status);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
if ui.add(egui::Button::new("force refresh theme")).clicked() {
|
if ui.add(egui::Button::new("force refresh theme")).clicked() {
|
||||||
catppuccin_egui::set_theme(ui.ctx(), self.settings.theme.into());
|
ui.set_theme(egui::Theme::from(self.settings.theme));
|
||||||
|
}
|
||||||
|
if ui.button("panic button").clicked() {
|
||||||
|
panic!("this is the panic button");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ui.collapsing("anti afk", |ui| {
|
ui.collapsing("anti afk", |ui| {
|
||||||
|
|||||||
+24
-16
@@ -3,47 +3,55 @@ use crate::{
|
|||||||
app::{App, WINDOW_SIZE},
|
app::{App, WINDOW_SIZE},
|
||||||
tools,
|
tools,
|
||||||
},
|
},
|
||||||
util::{persistent_state::PersistentState, win},
|
util::persistent_state::PersistentState,
|
||||||
};
|
};
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
|
use windows::Win32::System::Com::{COINIT_APARTMENTTHREADED, CoInitializeEx};
|
||||||
|
|
||||||
fn app_creator(
|
fn app_creator(
|
||||||
cc: &eframe::CreationContext<'_>,
|
cc: &eframe::CreationContext<'_>,
|
||||||
) -> Result<Box<dyn eframe::App>, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<Box<dyn eframe::App>, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
// initialize COM just in case
|
||||||
|
if let Err(e) = unsafe { CoInitializeEx(None, COINIT_APARTMENTTHREADED) }.ok() {
|
||||||
|
log::error!("couldn't initialize COM: {e}");
|
||||||
|
}
|
||||||
// initialize App early to modify some things before returning it
|
// initialize App early to modify some things before returning it
|
||||||
let mut app = Box::new(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;
|
persistent_state.apply_to(&mut app);
|
||||||
app.settings = persistent_state.settings;
|
|
||||||
}
|
|
||||||
// check if we're elevated. if not, and the user wants an elevated launch - relaunch elevated
|
|
||||||
if !app.flags.elevated && app.settings.start_elevated {
|
|
||||||
win::elevate(win::ElevationExitMethod::Forced);
|
|
||||||
}
|
}
|
||||||
// refresh system info because it initializes with nothing
|
// refresh system info because it initializes with nothing
|
||||||
app.system_info.refresh();
|
app.system_info.refresh();
|
||||||
// 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. we set the egui theme
|
// set our initial theme, from earlier loaded settings. we set the egui theme here to work
|
||||||
// to dark here to work around system theme based switching of the egui style
|
// around system theme based switching of the egui style
|
||||||
cc.egui_ctx.set_theme(egui::Theme::Dark);
|
cc.egui_ctx.set_theme(egui::Theme::from(app.settings.theme));
|
||||||
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.all_styles_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;
|
||||||
style.visuals.hyperlink_color = catppuccin_egui::Theme::from(app.settings.theme).text;
|
// style.visuals.hyperlink_color = catppuccin_egui::Theme::from(app.settings.theme).text;
|
||||||
});
|
});
|
||||||
// load any extra fonts that we need
|
// load any extra fonts that we need
|
||||||
let mut fonts = egui::FontDefinitions::default();
|
let mut fonts = egui::FontDefinitions::default();
|
||||||
fonts.font_data.insert(
|
fonts.font_data.insert(
|
||||||
"Ubuntu-Regular".to_owned(),
|
"Inter 18pt Light".to_owned(),
|
||||||
egui::FontData::from_static(include_bytes!("../../assets/Ubuntu-Regular.ttf")).into(),
|
egui::FontData::from_static(include_bytes!("../../assets/Inter_18pt-Light.ttf")).into(),
|
||||||
|
);
|
||||||
|
fonts
|
||||||
|
.families
|
||||||
|
.get_mut(&egui::FontFamily::Proportional)
|
||||||
|
.unwrap()
|
||||||
|
.insert(0, "Inter 18pt Light".to_owned());
|
||||||
|
fonts.font_data.insert(
|
||||||
|
"Inter 18pt Regular".to_owned(),
|
||||||
|
egui::FontData::from_static(include_bytes!("../../assets/Inter_18pt-Regular.ttf")).into(),
|
||||||
);
|
);
|
||||||
fonts.families.insert(
|
fonts.families.insert(
|
||||||
egui::FontFamily::Name("Ubuntu-Regular".into()),
|
egui::FontFamily::Name("Inter 18pt Regular".into()),
|
||||||
vec!["Ubuntu-Regular".to_owned()],
|
vec!["Inter 18pt Regular".to_owned()],
|
||||||
);
|
);
|
||||||
cc.egui_ctx.set_fonts(fonts);
|
cc.egui_ctx.set_fonts(fonts);
|
||||||
// finally return the App
|
// finally return the App
|
||||||
|
|||||||
+50
-32
@@ -1,7 +1,37 @@
|
|||||||
use crate::util::win;
|
use crate::util::win;
|
||||||
|
use eframe::egui;
|
||||||
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 = "Light")]
|
||||||
|
Light,
|
||||||
|
#[strum(to_string = "Dark")]
|
||||||
|
Dark,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Theme> for egui::Theme {
|
||||||
|
fn from(val: Theme) -> Self {
|
||||||
|
match val {
|
||||||
|
Theme::Auto => {
|
||||||
|
if win::is_system_theme_light() {
|
||||||
|
egui::Theme::Light
|
||||||
|
} else {
|
||||||
|
egui::Theme::Dark
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Theme::Light => egui::Theme::Light,
|
||||||
|
Theme::Dark => egui::Theme::Dark,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[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 +40,29 @@ 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 theme: Theme,
|
||||||
pub start_elevated: bool,
|
pub launch_version: LaunchVersion,
|
||||||
|
pub save_server_ip: String,
|
||||||
|
pub block_method: BlockMethod,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Settings {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
theme: Theme::default(),
|
||||||
|
launch_version: LaunchVersion::default(),
|
||||||
|
block_method: BlockMethod::default(),
|
||||||
|
save_server_ip: String::from(ROCKSTAR_SAVE_SERVER),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -11,12 +11,12 @@ pub fn load_icon() -> egui::IconData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn debug_keycombo_pressed(ctx: &egui::Context) -> bool {
|
pub fn debug_keycombo_pressed(ui: &egui::Ui) -> bool {
|
||||||
ctx.input(|i| i.modifiers.all() && i.key_pressed(egui::Key::D))
|
ui.input(|i| i.modifiers.all() && i.key_pressed(egui::Key::D))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn debug_viewport_close_pressed(ctx: &egui::Context) -> bool {
|
pub fn debug_viewport_close_pressed(ui: &egui::Ui) -> bool {
|
||||||
ctx.input(|i| {
|
ui.input(|i| {
|
||||||
i.raw
|
i.raw
|
||||||
.viewports
|
.viewports
|
||||||
.get(&egui::ViewportId::from_hash_of("debug_viewport"))
|
.get(&egui::ViewportId::from_hash_of("debug_viewport"))
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@ impl UiExt for egui::Ui {
|
|||||||
self.horizontal(|ui| {
|
self.horizontal(|ui| {
|
||||||
ui.label(egui::RichText::new(text).font(egui::FontId::new(
|
ui.label(egui::RichText::new(text).font(egui::FontId::new(
|
||||||
12.5,
|
12.5,
|
||||||
egui::FontFamily::Name("Ubuntu-Regular".into()),
|
egui::FontFamily::Name("Inter 18pt Regular".into()),
|
||||||
)));
|
)));
|
||||||
ui.add(egui::Separator::default().horizontal());
|
ui.add(egui::Separator::default().horizontal());
|
||||||
});
|
});
|
||||||
|
|||||||
+10
-7
@@ -1,26 +1,29 @@
|
|||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
compile_error!("This application must be compiled for Windows.");
|
|
||||||
|
|
||||||
mod features;
|
mod features;
|
||||||
mod gui;
|
mod gui;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
fn init_storage() {
|
fn init_storage() {
|
||||||
if !crate::util::consts::path::APP_STORAGE.exists() {
|
if !util::consts::path::APP_STORAGE.exists() {
|
||||||
std::fs::create_dir_all(crate::util::consts::path::APP_STORAGE.as_path()).unwrap();
|
std::fs::create_dir_all(util::consts::path::APP_STORAGE.as_path()).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn panic_hook(panic_info: &std::panic::PanicHookInfo<'_>) {
|
fn panic_hook(panic_info: &std::panic::PanicHookInfo<'_>) {
|
||||||
let backtrace = std::backtrace::Backtrace::capture();
|
let backtrace = std::backtrace::Backtrace::capture();
|
||||||
let message = format!("{panic_info}\nstack backtrace:\n{backtrace}\n");
|
let mut message = format!("{panic_info}");
|
||||||
crate::util::log::log(crate::util::log::LogLevel::Panic, &message);
|
if backtrace.status() == std::backtrace::BacktraceStatus::Captured {
|
||||||
|
write!(message, "\nstack backtrace:\n{backtrace}").unwrap();
|
||||||
|
}
|
||||||
|
log::error!("{message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
init_storage();
|
init_storage();
|
||||||
|
util::logging::Logger::init(log::LevelFilter::Info);
|
||||||
std::panic::set_hook(Box::new(panic_hook));
|
std::panic::set_hook(Box::new(panic_hook));
|
||||||
gui::run::run();
|
gui::run::run();
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -1,6 +1,7 @@
|
|||||||
pub mod consts;
|
pub mod consts;
|
||||||
pub mod countdown;
|
pub mod countdown;
|
||||||
pub mod log;
|
pub mod firewall;
|
||||||
|
pub mod logging;
|
||||||
pub mod persistent_state;
|
pub mod persistent_state;
|
||||||
pub mod system_info;
|
pub mod system_info;
|
||||||
pub mod win;
|
pub mod win;
|
||||||
|
|||||||
@@ -13,3 +13,10 @@ pub mod game {
|
|||||||
pub const EXE_LEGACY: &str = "GTA5.exe";
|
pub const EXE_LEGACY: &str = "GTA5.exe";
|
||||||
pub const WINDOW_TITLE: &str = "Grand Theft Auto V";
|
pub const WINDOW_TITLE: &str = "Grand Theft Auto V";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod colours {
|
||||||
|
use eframe::egui;
|
||||||
|
|
||||||
|
pub const RED: egui::Color32 = egui::Color32::from_rgb(255, 96, 96);
|
||||||
|
pub const GREEN: egui::Color32 = egui::Color32::from_rgb(96, 255, 96);
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use windows::{
|
||||||
|
Win32::{
|
||||||
|
NetworkManagement::WindowsFirewall::{
|
||||||
|
INetFwPolicy2, INetFwRule, NET_FW_ACTION_BLOCK, NET_FW_IP_PROTOCOL_ANY,
|
||||||
|
NET_FW_IP_PROTOCOL_TCP, NET_FW_IP_PROTOCOL_UDP, NET_FW_RULE_DIR_IN,
|
||||||
|
NET_FW_RULE_DIR_OUT, NetFwPolicy2, NetFwRule,
|
||||||
|
},
|
||||||
|
System::Com::{CLSCTX_INPROC_SERVER, CoCreateInstance},
|
||||||
|
},
|
||||||
|
core::BSTR,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Firewall {
|
||||||
|
policy: INetFwPolicy2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Firewall {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
policy: unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER) }.unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Firewall {
|
||||||
|
pub fn add(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
mode: RuleMode,
|
||||||
|
direction: RuleDirection,
|
||||||
|
protocol: RuleProtocol,
|
||||||
|
) -> Result<()> {
|
||||||
|
let add_rule = || {
|
||||||
|
let rules = unsafe { self.policy.Rules() }?;
|
||||||
|
unsafe { rules.Remove(&BSTR::from(name)) }?;
|
||||||
|
let rule: INetFwRule =
|
||||||
|
unsafe { CoCreateInstance(&NetFwRule, None, CLSCTX_INPROC_SERVER) }?;
|
||||||
|
unsafe { rule.SetName(&BSTR::from(name)) }?;
|
||||||
|
match mode {
|
||||||
|
RuleMode::Executable(exe) => {
|
||||||
|
unsafe { rule.SetApplicationName(&BSTR::from(exe.to_string_lossy().as_ref())) }?
|
||||||
|
}
|
||||||
|
RuleMode::Address(ip) => unsafe { rule.SetRemoteAddresses(&BSTR::from(ip)) }?,
|
||||||
|
}
|
||||||
|
match direction {
|
||||||
|
RuleDirection::In => unsafe { rule.SetDirection(NET_FW_RULE_DIR_IN) }?,
|
||||||
|
RuleDirection::Out => unsafe { rule.SetDirection(NET_FW_RULE_DIR_OUT) }?,
|
||||||
|
}
|
||||||
|
unsafe { rule.SetEnabled(true.into()) }?;
|
||||||
|
unsafe { rule.SetAction(NET_FW_ACTION_BLOCK) }?;
|
||||||
|
match protocol {
|
||||||
|
RuleProtocol::Any => unsafe { rule.SetProtocol(NET_FW_IP_PROTOCOL_ANY.0) }?,
|
||||||
|
RuleProtocol::Tcp => unsafe { rule.SetProtocol(NET_FW_IP_PROTOCOL_TCP.0) }?,
|
||||||
|
RuleProtocol::Udp => unsafe { rule.SetProtocol(NET_FW_IP_PROTOCOL_UDP.0) }?,
|
||||||
|
}
|
||||||
|
unsafe { rules.Add(&rule) }?;
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
add_rule().inspect_err(|e| log::warn!("Failed to add rule '{name}': {e}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(&self, name: &str) -> Result<()> {
|
||||||
|
let remove_rule = || {
|
||||||
|
let rules = unsafe { self.policy.Rules() }?;
|
||||||
|
unsafe { rules.Remove(&BSTR::from(name)) }?;
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
remove_rule().inspect_err(|e| log::warn!("Failed to remove rule '{name}': {e}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_blocked(&self, name: &str) -> Result<bool> {
|
||||||
|
let rules = unsafe { self.policy.Rules() }?;
|
||||||
|
let rule_exists = unsafe { rules.Item(&BSTR::from(name)) }.is_ok();
|
||||||
|
Ok(rule_exists)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum RuleMode {
|
||||||
|
Executable(PathBuf),
|
||||||
|
Address(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum RuleDirection {
|
||||||
|
In,
|
||||||
|
Out,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum RuleProtocol {
|
||||||
|
Any,
|
||||||
|
#[allow(unused)]
|
||||||
|
Tcp,
|
||||||
|
Udp,
|
||||||
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
use crate::util::consts::path;
|
|
||||||
use std::{fs::File, io::Write};
|
|
||||||
use strum::Display;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Display)]
|
|
||||||
pub enum LogLevel {
|
|
||||||
Error,
|
|
||||||
Panic,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn log(level: LogLevel, message: &str) {
|
|
||||||
let mut file = File::options()
|
|
||||||
.create(true)
|
|
||||||
.append(true)
|
|
||||||
.open(path::APP_LOG.as_path())
|
|
||||||
.unwrap();
|
|
||||||
let timestamp = chrono::Local::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, false);
|
|
||||||
let message = format!("[{timestamp}] [{level}]\n{message}\n\n");
|
|
||||||
file.write_all(message.as_bytes()).unwrap();
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
io::Write,
|
||||||
|
sync::{LazyLock, Mutex},
|
||||||
|
time::SystemTime,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Logger {
|
||||||
|
file: Mutex<File>,
|
||||||
|
}
|
||||||
|
|
||||||
|
static LOGGER: LazyLock<Logger> = LazyLock::new(|| Logger {
|
||||||
|
file: File::options()
|
||||||
|
.create(true)
|
||||||
|
.append(true)
|
||||||
|
.open(crate::util::consts::path::APP_LOG.as_path())
|
||||||
|
.unwrap()
|
||||||
|
.into(),
|
||||||
|
});
|
||||||
|
|
||||||
|
impl log::Log for Logger {
|
||||||
|
fn enabled(&self, metadata: &log::Metadata) -> bool {
|
||||||
|
metadata.level() <= log::Level::Info
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&self) {}
|
||||||
|
|
||||||
|
fn log(&self, record: &log::Record) {
|
||||||
|
if self.enabled(record.metadata()) {
|
||||||
|
Self::log_to_stderr(record);
|
||||||
|
self.log_to_file(record);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Logger {
|
||||||
|
pub fn init(level: log::LevelFilter) {
|
||||||
|
log::set_logger(&*LOGGER)
|
||||||
|
.map(|()| log::set_max_level(level))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log_to_file(&self, record: &log::Record) {
|
||||||
|
let mut file = self.file.lock().unwrap();
|
||||||
|
writeln!(
|
||||||
|
file,
|
||||||
|
"[{}][{}] {}",
|
||||||
|
humantime::format_rfc3339_seconds(SystemTime::now()),
|
||||||
|
record.level(),
|
||||||
|
record.args()
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
file.flush().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log_to_stderr(record: &log::Record) {
|
||||||
|
eprintln!(
|
||||||
|
"[{}][{}] {}",
|
||||||
|
humantime::format_rfc3339_seconds(SystemTime::now()),
|
||||||
|
record.level(),
|
||||||
|
record.args()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
use crate::{features::launch::Platform, gui::settings::Settings, util::consts::path};
|
use crate::{
|
||||||
|
features::launch::Platform,
|
||||||
|
gui::{app, settings::Settings},
|
||||||
|
util::consts::path,
|
||||||
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
fs::{self, File},
|
fs::{self, File},
|
||||||
@@ -8,6 +12,7 @@ use std::{
|
|||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct PersistentState {
|
pub struct PersistentState {
|
||||||
pub launcher: Platform,
|
pub launcher: Platform,
|
||||||
|
pub anti_afk_enabled: bool,
|
||||||
pub settings: Settings,
|
pub settings: Settings,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,4 +28,15 @@ impl PersistentState {
|
|||||||
let json = serde_json::to_string_pretty(&self).unwrap();
|
let json = serde_json::to_string_pretty(&self).unwrap();
|
||||||
config_file.write_all(json.as_bytes()).unwrap();
|
config_file.write_all(json.as_bytes()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn apply_to(self, app: &mut app::App) {
|
||||||
|
let Self {
|
||||||
|
launcher,
|
||||||
|
anti_afk_enabled,
|
||||||
|
settings,
|
||||||
|
} = self;
|
||||||
|
app.launch.selected = launcher;
|
||||||
|
app.anti_afk.enabled = anti_afk_enabled;
|
||||||
|
app.settings = settings;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-25
@@ -1,12 +1,13 @@
|
|||||||
use std::{
|
use std::{
|
||||||
os::windows::process::CommandExt,
|
ffi::{OsStr, OsString},
|
||||||
|
os::windows::{ffi::OsStringExt, process::CommandExt},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::Command,
|
process::Command,
|
||||||
};
|
};
|
||||||
use windows::{
|
use windows::{
|
||||||
Win32::System::{
|
Win32::System::{
|
||||||
Diagnostics::ToolHelp::{
|
Diagnostics::ToolHelp::{
|
||||||
CreateToolhelp32Snapshot, PROCESSENTRY32, Process32First, Process32Next,
|
CreateToolhelp32Snapshot, PROCESSENTRY32W, Process32FirstW, Process32NextW,
|
||||||
TH32CS_SNAPPROCESS,
|
TH32CS_SNAPPROCESS,
|
||||||
},
|
},
|
||||||
Threading::{
|
Threading::{
|
||||||
@@ -17,19 +18,21 @@ use windows::{
|
|||||||
core::PWSTR,
|
core::PWSTR,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::util::consts::game::{EXE_ENHANCED, EXE_LEGACY};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Process {
|
pub struct Process {
|
||||||
pid: u32,
|
pid: u32,
|
||||||
name: String,
|
name: OsString,
|
||||||
exe: Option<PathBuf>,
|
exe: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Process {
|
impl Process {
|
||||||
pub fn pid(&self) -> u32 {
|
pub const fn pid(&self) -> u32 {
|
||||||
self.pid
|
self.pid
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn name(&self) -> &str {
|
pub fn name(&self) -> &OsStr {
|
||||||
&self.name
|
&self.name
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,35 +59,43 @@ pub struct SystemInfo {
|
|||||||
impl SystemInfo {
|
impl SystemInfo {
|
||||||
pub fn refresh(&mut self) {
|
pub fn refresh(&mut self) {
|
||||||
let mut processes = Vec::new();
|
let mut processes = Vec::new();
|
||||||
let snapshot_handle = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0).unwrap() };
|
let snapshot_handle = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) }.unwrap();
|
||||||
let mut process_entry = PROCESSENTRY32 {
|
let mut process_entry = PROCESSENTRY32W {
|
||||||
dwSize: size_of::<PROCESSENTRY32>() as u32,
|
dwSize: size_of::<PROCESSENTRY32W>() as u32,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
unsafe { Process32First(snapshot_handle, &raw mut process_entry).unwrap() };
|
unsafe { Process32FirstW(snapshot_handle, &raw mut process_entry) }.unwrap();
|
||||||
let exe_full_path = get_exe_full_path(&process_entry);
|
let exe_full_path = get_exe_full_path(&process_entry);
|
||||||
processes.push(Process {
|
processes.push(Process {
|
||||||
pid: process_entry.th32ProcessID,
|
pid: process_entry.th32ProcessID,
|
||||||
name: c_char_arr_to_string(&process_entry.szExeFile),
|
name: wide_array_to_os_string(&process_entry.szExeFile),
|
||||||
exe: exe_full_path,
|
exe: exe_full_path,
|
||||||
});
|
});
|
||||||
while unsafe { Process32Next(snapshot_handle, &raw mut process_entry) }.is_ok() {
|
while unsafe { Process32NextW(snapshot_handle, &raw mut process_entry) }.is_ok() {
|
||||||
let exe_full_path = get_exe_full_path(&process_entry);
|
let exe_full_path = get_exe_full_path(&process_entry);
|
||||||
processes.push(Process {
|
processes.push(Process {
|
||||||
pid: process_entry.th32ProcessID,
|
pid: process_entry.th32ProcessID,
|
||||||
name: c_char_arr_to_string(&process_entry.szExeFile),
|
name: wide_array_to_os_string(&process_entry.szExeFile),
|
||||||
exe: exe_full_path,
|
exe: exe_full_path,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
self.processes = processes;
|
self.processes = processes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_game_exe_path(&mut self) -> Option<&Path> {
|
||||||
|
self.refresh();
|
||||||
|
self.processes()
|
||||||
|
.iter()
|
||||||
|
.find(|p| p.name() == EXE_ENHANCED || p.name() == EXE_LEGACY)
|
||||||
|
.and_then(|p| p.exe())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn processes(&self) -> &[Process] {
|
pub fn processes(&self) -> &[Process] {
|
||||||
&self.processes
|
&self.processes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_exe_full_path(process_entry: &PROCESSENTRY32) -> Option<PathBuf> {
|
fn get_exe_full_path(process_entry: &PROCESSENTRY32W) -> Option<PathBuf> {
|
||||||
let process_handle_result = unsafe {
|
let process_handle_result = unsafe {
|
||||||
OpenProcess(
|
OpenProcess(
|
||||||
PROCESS_QUERY_LIMITED_INFORMATION,
|
PROCESS_QUERY_LIMITED_INFORMATION,
|
||||||
@@ -92,29 +103,25 @@ fn get_exe_full_path(process_entry: &PROCESSENTRY32) -> Option<PathBuf> {
|
|||||||
process_entry.th32ProcessID,
|
process_entry.th32ProcessID,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
let mut exename = [0u16; 260];
|
|
||||||
let mut dwsize = exename.len() as u32;
|
|
||||||
process_handle_result.map_or(None, |process_handle| {
|
process_handle_result.map_or(None, |process_handle| {
|
||||||
|
let mut exe_name = [0u16; 260];
|
||||||
|
let mut dw_size = exe_name.len() as u32;
|
||||||
let image_name_result = unsafe {
|
let image_name_result = unsafe {
|
||||||
QueryFullProcessImageNameW(
|
QueryFullProcessImageNameW(
|
||||||
process_handle,
|
process_handle,
|
||||||
PROCESS_NAME_WIN32,
|
PROCESS_NAME_WIN32,
|
||||||
PWSTR(exename.as_mut_ptr()),
|
PWSTR(exe_name.as_mut_ptr()),
|
||||||
&raw mut dwsize,
|
&raw mut dw_size,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
match image_name_result {
|
match image_name_result {
|
||||||
Ok(()) => Some(PathBuf::from(
|
Ok(()) => Some(PathBuf::from(wide_array_to_os_string(&exe_name))),
|
||||||
unsafe { PWSTR(exename.as_mut_ptr()).to_string() }.unwrap(),
|
|
||||||
)),
|
|
||||||
Err(_) => None,
|
Err(_) => None,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn c_char_arr_to_string(arr: &[i8]) -> String {
|
fn wide_array_to_os_string(wide: &[u16]) -> OsString {
|
||||||
arr.iter()
|
let null_pos = wide.iter().position(|&x| x == 0).unwrap_or(wide.len());
|
||||||
.take_while(|&&b| b != 0)
|
OsString::from_wide(&wide[..null_pos])
|
||||||
.map(|&b| b as u8 as char)
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-65
@@ -1,90 +1,33 @@
|
|||||||
use windows::{
|
use windows::Win32::UI::{
|
||||||
Win32::{
|
|
||||||
Foundation::{CloseHandle, HANDLE},
|
|
||||||
Security::{GetTokenInformation, TOKEN_ELEVATION, TOKEN_QUERY, TokenElevation},
|
|
||||||
System::Threading::{GetCurrentProcess, OpenProcessToken},
|
|
||||||
UI::{
|
|
||||||
Input::KeyboardAndMouse::{GetAsyncKeyState, VIRTUAL_KEY},
|
Input::KeyboardAndMouse::{GetAsyncKeyState, VIRTUAL_KEY},
|
||||||
Shell::ShellExecuteW,
|
|
||||||
WindowsAndMessaging::{
|
WindowsAndMessaging::{
|
||||||
CURSOR_SHOWING, CURSORINFO, GetCursorInfo, GetForegroundWindow, GetWindowTextW,
|
CURSOR_SHOWING, CURSORINFO, GetCursorInfo, GetForegroundWindow, GetWindowTextW,
|
||||||
SW_NORMAL,
|
|
||||||
},
|
},
|
||||||
},
|
|
||||||
},
|
|
||||||
core::{HSTRING, PCWSTR},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub enum ElevationExitMethod<'a> {
|
|
||||||
Gentle(&'a mut bool),
|
|
||||||
Forced,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_cursor_visible() -> bool {
|
pub fn is_cursor_visible() -> bool {
|
||||||
let mut ci = CURSORINFO {
|
let mut ci = CURSORINFO {
|
||||||
cbSize: u32::try_from(std::mem::size_of::<CURSORINFO>()).unwrap(),
|
cbSize: u32::try_from(std::mem::size_of::<CURSORINFO>()).unwrap(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
unsafe {
|
unsafe { GetCursorInfo(&raw mut ci) }.unwrap();
|
||||||
GetCursorInfo(&raw mut ci).unwrap();
|
|
||||||
}
|
|
||||||
ci.flags == CURSOR_SHOWING
|
ci.flags == CURSOR_SHOWING
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_window_focused(target_title: &str) -> bool {
|
pub fn is_window_focused(target_title: &str) -> bool {
|
||||||
let mut buffer = [0; 512];
|
let mut buffer = [0; 512];
|
||||||
unsafe {
|
let hwnd = unsafe { GetForegroundWindow() };
|
||||||
let hwnd = GetForegroundWindow();
|
let length = unsafe { GetWindowTextW(hwnd, &mut buffer) };
|
||||||
let length = GetWindowTextW(hwnd, &mut buffer);
|
|
||||||
let current_title = String::from_utf16_lossy(&buffer[..length as usize]);
|
let current_title = String::from_utf16_lossy(&buffer[..length as usize]);
|
||||||
current_title == target_title
|
current_title == target_title
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_any_key_pressed(keys: &[VIRTUAL_KEY]) -> bool {
|
pub fn is_any_key_pressed(keys: &[VIRTUAL_KEY]) -> bool {
|
||||||
keys.iter()
|
keys.iter()
|
||||||
.any(|&key| unsafe { (GetAsyncKeyState(i32::from(key.0)) & i16::MIN) != 0 })
|
.any(|&key| unsafe { GetAsyncKeyState(i32::from(key.0)) } & i16::MIN != 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn elevate(closing: ElevationExitMethod) {
|
pub fn is_system_theme_light() -> bool {
|
||||||
let exe = std::env::current_exe().unwrap();
|
|
||||||
unsafe {
|
|
||||||
ShellExecuteW(
|
|
||||||
None,
|
|
||||||
&HSTRING::from("runas"),
|
|
||||||
&HSTRING::from(exe.as_path()),
|
|
||||||
PCWSTR::null(),
|
|
||||||
PCWSTR::null(),
|
|
||||||
SW_NORMAL,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
match closing {
|
|
||||||
ElevationExitMethod::Gentle(closing) => *closing = true,
|
|
||||||
ElevationExitMethod::Forced => std::process::exit(0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_elevated() -> bool {
|
|
||||||
let mut token: HANDLE = HANDLE::default();
|
|
||||||
unsafe {
|
|
||||||
if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &raw mut token).is_err() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let mut elevation = TOKEN_ELEVATION::default();
|
|
||||||
let mut size = u32::try_from(std::mem::size_of::<TOKEN_ELEVATION>()).unwrap();
|
|
||||||
let result = GetTokenInformation(
|
|
||||||
token,
|
|
||||||
TokenElevation,
|
|
||||||
Some((&raw mut elevation).cast()),
|
|
||||||
size,
|
|
||||||
&raw mut size,
|
|
||||||
);
|
|
||||||
CloseHandle(token).unwrap();
|
|
||||||
result.is_ok() && elevation.TokenIsElevated != 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_system_theme_dark() -> 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) =
|
||||||
@@ -92,8 +35,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