52 Commits
Author SHA1 Message Date
futile aeb99992bb 0.10.0 2025-11-07 12:26:12 +00:00
futile 8ab6e14560 show git commit hash as monospace 2025-11-07 11:59:57 +00:00
futile b9ebdfdb5c update force close game button width to fit new font 2025-11-07 11:56:26 +00:00
futile 2804812691 replace Ubuntu fonts with Inter 2025-11-07 11:51:38 +00:00
futile 606fb7e191 update readme 2025-11-07 11:37:13 +00:00
futile e2a779b7f5 redo links in about stage a bit 2025-11-07 11:35:56 +00:00
futile a8b04bb72b move colour constants from gui::colours to util::consts::colours 2025-11-07 00:34:42 +00:00
futile c34cf187d5 ensure persistent_state is easier and more infallible to add fields to 2025-11-06 23:55:04 +00:00
futile f07421e75a use newer syntax for cargo instructions in build.rs 2025-11-06 08:53:09 +00:00
futile 8b4a455dc1 make empty_session::activate simply return a bool 2025-11-06 08:45:50 +00:00
futile a1300f8d76 lowkey improve logging 2025-11-06 08:41:53 +00:00
futile 9e623219a5 shorten some paths in main.rs 2025-11-06 05:07:49 +00:00
futile 09e19b6e16 remove hardly useful "windows compilation only" warning 2025-11-06 05:06:57 +00:00
futile 4498c45965 apply clippy suggestion 2025-11-06 04:20:40 +00:00
futile af8a5b1746 persist anti afk toggle 2025-11-06 04:17:03 +00:00
futile 560d9659f4 make git hash embedding more reliable 2025-11-06 03:06:25 +00:00
futile bf086d0442 amend comment 2025-11-06 02:38:13 +00:00
futile a6ea8ad8e2 ignore return of NtResumeProcess in empty_session::deactivate to reduce crashing 2025-11-06 02:37:26 +00:00
futile 4c00d1846b add git commit hash when hovering version info 2025-11-05 00:33:17 +00:00
futile e5e335f4f3 apply clippy suggestion 2025-11-05 00:10:39 +00:00
futile 3ab5987b67 use more unwraps instead of let _ = 2025-11-05 00:08:53 +00:00
futile 2030796337 add description to Cargo.toml after making sure tauri-winres doesn't use it 2025-11-04 23:08:59 +00:00
futile 2edb4f492a get rid of justfile it sucks 2025-11-04 23:08:31 +00:00
futile 114292d912 use tauri-winres instead of winresource & specify some more fields in the windows resource in build.rs 2025-11-04 22:56:03 +00:00
futile 8adc1f49ad add license and author information to Cargo.toml 2025-11-04 20:15:09 +00:00
futile bb27334f57 0.9.0 2025-11-03 07:43:40 +00:00
futile 1e13905885 update codebase for github 2025-11-03 07:13:00 +00:00
futile 8fc73b2992 update readme for github 2025-11-03 06:56:28 +00:00
futile 43b72995dd create github workflow to build and release on tag push 2025-11-03 06:42:22 +00:00
futile d3473d46e3 create github workflow to rebuild and rerelease all existing tags 2025-11-03 05:26:46 +00:00
futile 1363f5b125 use match instead of if for LaunchVersion checks 2025-10-25 09:35:31 +01:00
futile d15b8070ec improve strings in util::system_info 2025-10-11 03:32:39 +01:00
futile efccd6ed50 update readme 2025-09-13 16:58:52 +01:00
futile 5c73ee7bd5 improve (tighten) scoping of unsafe blocks 2025-09-13 16:50:44 +01:00
futile 445b63fa4c small system_info improvements 2025-09-13 16:39:16 +01:00
futile 941868dcc8 use &[c_char] instead of &[i8] in c_char_arr_to_string 2025-09-13 12:02:19 +01:00
futile 762a3b9b26 update readme 2025-09-13 00:34:48 +01:00
futile 0cb211c103 0.8.0 2025-09-12 05:17:47 +01:00
futile 90b32c5c5a this couldn't hurt to be create_dir_all instead of create_dir 2025-09-12 05:12:11 +01:00
futile 71c3c69ea2 a few clippy fixes 2025-09-12 05:04:12 +01:00
futile b553e418f5 reduce clutter in system_info 2025-09-12 01:21:56 +01:00
futile ab026c0bc6 adapt codebase to own, new system info impl 2025-09-12 01:07:10 +01:00
futile 32e21f58a1 create a clone of sysinfo focused on only our needs 2025-09-12 00:52:19 +01:00
futile b0c339cc5f update readme 2025-09-09 14:09:17 +01:00
futile c2584b5d01 reimplement "update checking" in the form of a simple button that goes to latest release 2025-09-09 14:07:46 +01:00
futile 3322f9582f update readme 2025-09-09 03:16:00 +01:00
futile 5348a1d6ee rip out update checking (too unreliable) 2025-09-09 03:15:01 +01:00
futile 05ce68cb47 update readme 2025-09-09 03:03:36 +01:00
futile f63d24ea43 improve reliability and logging when running into game access denied issues 2025-09-09 03:03:32 +01:00
futile 356d5f17e5 me when i don't smoke the drugs that make you write retarded code 2025-09-08 14:32:36 +01:00
futile bc5e80aaca update readme 2025-09-06 21:49:44 +01:00
futile e050231692 update readme 2025-09-06 12:26:38 +01:00
31 changed files with 626 additions and 774 deletions
+31
View File
@@ -0,0 +1,31 @@
name: Create release for pushed tag
on:
push:
tags:
- "*"
jobs:
release-on-tag:
runs-on: windows-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@stable
- name: Create release
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ github.ref_name }}
run: |
if gh release view "$TAG" >/dev/null 2>&1; then
echo "A release already exists for $TAG, skipping"
else
cargo build --release
prev_tag=$(gh release list --limit 100 --json tagName --jq '.[0].tagName')
notes="**Full Changelog**: https://github.com/${{ github.repository }}/compare/${prev_tag}...${TAG}"
gh release create "$TAG" target/release/gta-tools.exe \
--title "$TAG" \
--notes "$notes"
fi
@@ -0,0 +1,43 @@
name: Create releases for existing tags
on:
workflow_dispatch:
jobs:
release-retroactively:
runs-on: windows-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Get tags and create release for each
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
mapfile -t tags < <(git tag -l)
echo "${#tags[@]} tags found."
prev_tag=""
for tag in "${tags[@]}"; do
if gh release view "$tag" >/dev/null 2>&1; then
echo "A release already exists for $tag, skipping"
else
echo "Proceeding with tag $tag"
git checkout "$tag"
cargo build --release
if [ -n "$prev_tag" ]; then
notes="**Full Changelog**: https://github.com/${{ github.repository }}/compare/${prev_tag}...${tag}"
else
notes="**Full Changelog**: https://github.com/${{ github.repository }}/commits/${tag}"
fi
gh release create "$tag" target/release/gta-tools.exe \
--title "$tag" \
--notes "$notes"
fi
prev_tag="$tag"
done
Generated
+119 -458
View File
@@ -63,21 +63,6 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "arboard" name = "arboard"
version = "3.6.1" version = "3.6.1"
@@ -261,18 +246,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "chrono"
version = "0.4.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
dependencies = [
"android-tzdata",
"iana-time-zone",
"num-traits",
"windows-link 0.1.3",
]
[[package]] [[package]]
name = "clipboard-win" name = "clipboard-win"
version = "5.4.1" version = "5.4.1"
@@ -301,35 +274,6 @@ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]]
name = "cookie"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
dependencies = [
"percent-encoding",
"time",
"version_check",
]
[[package]]
name = "cookie_store"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fc4bff745c9b4c7fb1e97b25d13153da2bc7796260141df62378998d070207f"
dependencies = [
"cookie",
"document-features",
"idna",
"indexmap",
"log",
"serde",
"serde_derive",
"serde_json",
"time",
"url",
]
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.9.4" version = "0.9.4"
@@ -413,15 +357,6 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376"
[[package]]
name = "deranged"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc"
dependencies = [
"powerfmt",
]
[[package]] [[package]]
name = "dispatch" name = "dispatch"
version = "0.2.0" version = "0.2.0"
@@ -601,6 +536,20 @@ dependencies = [
"bytemuck", "bytemuck",
] ]
[[package]]
name = "embed-resource"
version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55a075fc573c64510038d7ee9abc7990635863992f83ebc52c8b433b8411a02e"
dependencies = [
"cc",
"memchr",
"rustc_version",
"toml",
"vswhom",
"winreg",
]
[[package]] [[package]]
name = "enum-map" name = "enum-map"
version = "2.7.3" version = "2.7.3"
@@ -727,12 +676,6 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]] [[package]]
name = "foreign-types" name = "foreign-types"
version = "0.5.0" version = "0.5.0"
@@ -779,17 +722,6 @@ dependencies = [
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]
[[package]]
name = "getrandom"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [
"cfg-if",
"libc",
"wasi 0.11.1+wasi-snapshot-preview1",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.3.3" version = "0.3.3"
@@ -799,7 +731,7 @@ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"r-efi", "r-efi",
"wasi 0.14.3+wasi-0.2.4", "wasi",
] ]
[[package]] [[package]]
@@ -880,24 +812,22 @@ dependencies = [
[[package]] [[package]]
name = "gta-tools" name = "gta-tools"
version = "0.7.0" version = "0.10.0"
dependencies = [ dependencies = [
"catppuccin-egui", "catppuccin-egui",
"chrono",
"eframe", "eframe",
"egui_extras", "egui_extras",
"humantime",
"image", "image",
"log",
"open", "open",
"semver",
"serde", "serde",
"serde_json", "serde_json",
"static_vcruntime", "static_vcruntime",
"strum", "strum",
"sysinfo", "tauri-winres",
"ureq", "windows",
"windows 0.62.0",
"winreg", "winreg",
"winresource",
] ]
[[package]] [[package]]
@@ -912,9 +842,9 @@ dependencies = [
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.5" version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
[[package]] [[package]]
name = "heck" name = "heck"
@@ -929,45 +859,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
[[package]] [[package]]
name = "http" name = "humantime"
version = "1.3.1" version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "httparse"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]]
name = "iana-time-zone"
version = "0.1.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"log",
"wasm-bindgen",
"windows-core 0.61.2",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "icu_collections" name = "icu_collections"
@@ -1098,9 +993,9 @@ checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285"
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.11.0" version = "2.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown", "hashbrown",
@@ -1159,7 +1054,7 @@ version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
dependencies = [ dependencies = [
"getrandom 0.3.3", "getrandom",
"libc", "libc",
] ]
@@ -1337,21 +1232,6 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
[[package]]
name = "ntapi"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
dependencies = [
"winapi",
]
[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.19" version = "0.2.19"
@@ -1551,16 +1431,6 @@ dependencies = [
"objc2-core-foundation", "objc2-core-foundation",
] ]
[[package]]
name = "objc2-io-kit"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a"
dependencies = [
"libc",
"objc2-core-foundation",
]
[[package]] [[package]]
name = "objc2-io-surface" name = "objc2-io-surface"
version = "0.3.1" version = "0.3.1"
@@ -1821,12 +1691,6 @@ dependencies = [
"zerovec", "zerovec",
] ]
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]] [[package]]
name = "proc-macro-crate" name = "proc-macro-crate"
version = "3.3.0" version = "3.3.0"
@@ -1937,26 +1801,21 @@ dependencies = [
"bytemuck", "bytemuck",
] ]
[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.16",
"libc",
"untrusted",
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "roxmltree" name = "roxmltree"
version = "0.20.0" version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver",
]
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.44" version = "0.38.44"
@@ -1983,50 +1842,6 @@ dependencies = [
"windows-sys 0.60.2", "windows-sys 0.60.2",
] ]
[[package]]
name = "rustls"
version = "0.23.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc"
dependencies = [
"log",
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-pemfile"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "rustls-pki-types"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
dependencies = [
"zeroize",
]
[[package]]
name = "rustls-webpki"
version = "0.103.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.22" version = "1.0.22"
@@ -2062,24 +1877,34 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]] [[package]]
name = "semver" name = "semver"
version = "1.0.26" version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.219" version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.219" version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -2100,11 +1925,11 @@ dependencies = [
[[package]] [[package]]
name = "serde_spanned" name = "serde_spanned"
version = "0.6.9" version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392"
dependencies = [ dependencies = [
"serde", "serde_core",
] ]
[[package]] [[package]]
@@ -2248,12 +2073,6 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]] [[package]]
name = "svgtypes" name = "svgtypes"
version = "0.15.3" version = "0.15.3"
@@ -2287,17 +2106,13 @@ dependencies = [
] ]
[[package]] [[package]]
name = "sysinfo" name = "tauri-winres"
version = "0.37.0" version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07cec4dc2d2e357ca1e610cfb07de2fa7a10fc3e9fe89f72545f3d244ea87753" checksum = "fd21509dd1fa9bd355dc29894a6ff10635880732396aa38c0066c1e6c1ab8074"
dependencies = [ dependencies = [
"libc", "embed-resource",
"memchr", "toml",
"ntapi",
"objc2-core-foundation",
"objc2-io-kit",
"windows 0.61.3",
] ]
[[package]] [[package]]
@@ -2334,36 +2149,6 @@ dependencies = [
"zune-jpeg", "zune-jpeg",
] ]
[[package]]
name = "time"
version = "0.3.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83bde6f1ec10e72d583d91623c939f623002284ef622b87de38cfd546cbf2031"
dependencies = [
"deranged",
"num-conv",
"powerfmt",
"serde",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
[[package]]
name = "time-macros"
version = "0.2.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3"
dependencies = [
"num-conv",
"time-core",
]
[[package]] [[package]]
name = "tiny-skia" name = "tiny-skia"
version = "0.11.4" version = "0.11.4"
@@ -2402,14 +2187,17 @@ dependencies = [
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.8.23" version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8"
dependencies = [ dependencies = [
"serde", "indexmap",
"serde_core",
"serde_spanned", "serde_spanned",
"toml_datetime", "toml_datetime 0.7.3",
"toml_edit", "toml_parser",
"toml_writer",
"winnow",
] ]
[[package]] [[package]]
@@ -2417,8 +2205,14 @@ name = "toml_datetime"
version = "0.6.11" version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
[[package]]
name = "toml_datetime"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533"
dependencies = [ dependencies = [
"serde", "serde_core",
] ]
[[package]] [[package]]
@@ -2428,18 +2222,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"serde", "toml_datetime 0.6.11",
"serde_spanned",
"toml_datetime",
"toml_write",
"winnow", "winnow",
] ]
[[package]] [[package]]
name = "toml_write" name = "toml_parser"
version = "0.1.2" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e"
dependencies = [
"winnow",
]
[[package]]
name = "toml_writer"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2"
[[package]] [[package]]
name = "tracing" name = "tracing"
@@ -2475,45 +2275,6 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "ureq"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00432f493971db5d8e47a65aeb3b02f8226b9b11f1450ff86bb772776ebadd70"
dependencies = [
"base64",
"cookie_store",
"flate2",
"log",
"percent-encoding",
"rustls",
"rustls-pemfile",
"rustls-pki-types",
"serde",
"serde_json",
"ureq-proto",
"utf-8",
"webpki-roots",
]
[[package]]
name = "ureq-proto"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbe120bb823a0061680e66e9075942fcdba06d46551548c2c259766b9558bc9a"
dependencies = [
"base64",
"http",
"httparse",
"log",
]
[[package]] [[package]]
name = "url" name = "url"
version = "2.5.7" version = "2.5.7"
@@ -2548,12 +2309,6 @@ dependencies = [
"xmlwriter", "xmlwriter",
] ]
[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]] [[package]]
name = "utf8_iter" name = "utf8_iter"
version = "1.0.4" version = "1.0.4"
@@ -2566,6 +2321,26 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "vswhom"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b"
dependencies = [
"libc",
"vswhom-sys",
]
[[package]]
name = "vswhom-sys"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150"
dependencies = [
"cc",
"libc",
]
[[package]] [[package]]
name = "walkdir" name = "walkdir"
version = "2.5.0" version = "2.5.0"
@@ -2576,12 +2351,6 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.14.3+wasi-0.2.4" version = "0.14.3+wasi-0.2.4"
@@ -2795,15 +2564,6 @@ dependencies = [
"web-sys", "web-sys",
] ]
[[package]]
name = "webpki-roots"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2"
dependencies = [
"rustls-pki-types",
]
[[package]] [[package]]
name = "weezl" name = "weezl"
version = "0.1.10" version = "0.1.10"
@@ -2841,39 +2601,17 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.61.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
dependencies = [
"windows-collections 0.2.0",
"windows-core 0.61.2",
"windows-future 0.2.1",
"windows-link 0.1.3",
"windows-numerics 0.2.0",
]
[[package]] [[package]]
name = "windows" name = "windows"
version = "0.62.0" version = "0.62.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9579d0e6970fd5250aa29aba5994052385ff55cf7b28a059e484bb79ea842e42" checksum = "9579d0e6970fd5250aa29aba5994052385ff55cf7b28a059e484bb79ea842e42"
dependencies = [ dependencies = [
"windows-collections 0.3.0", "windows-collections",
"windows-core 0.62.0", "windows-core",
"windows-future 0.3.0", "windows-future",
"windows-link 0.2.0", "windows-link 0.2.0",
"windows-numerics 0.3.0", "windows-numerics",
]
[[package]]
name = "windows-collections"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
dependencies = [
"windows-core 0.61.2",
] ]
[[package]] [[package]]
@@ -2882,20 +2620,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a90dd7a7b86859ec4cdf864658b311545ef19dbcf17a672b52ab7cefe80c336f" checksum = "a90dd7a7b86859ec4cdf864658b311545ef19dbcf17a672b52ab7cefe80c336f"
dependencies = [ dependencies = [
"windows-core 0.62.0", "windows-core",
]
[[package]]
name = "windows-core"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link 0.1.3",
"windows-result 0.3.4",
"windows-strings 0.4.2",
] ]
[[package]] [[package]]
@@ -2907,19 +2632,8 @@ dependencies = [
"windows-implement", "windows-implement",
"windows-interface", "windows-interface",
"windows-link 0.2.0", "windows-link 0.2.0",
"windows-result 0.4.0", "windows-result",
"windows-strings 0.5.0", "windows-strings",
]
[[package]]
name = "windows-future"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
dependencies = [
"windows-core 0.61.2",
"windows-link 0.1.3",
"windows-threading 0.1.0",
] ]
[[package]] [[package]]
@@ -2928,9 +2642,9 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2194dee901458cb79e1148a4e9aac2b164cc95fa431891e7b296ff0b2f1d8a6" checksum = "b2194dee901458cb79e1148a4e9aac2b164cc95fa431891e7b296ff0b2f1d8a6"
dependencies = [ dependencies = [
"windows-core 0.62.0", "windows-core",
"windows-link 0.2.0", "windows-link 0.2.0",
"windows-threading 0.2.0", "windows-threading",
] ]
[[package]] [[package]]
@@ -2967,35 +2681,16 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
[[package]]
name = "windows-numerics"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
dependencies = [
"windows-core 0.61.2",
"windows-link 0.1.3",
]
[[package]] [[package]]
name = "windows-numerics" name = "windows-numerics"
version = "0.3.0" version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ce3498fe0aba81e62e477408383196b4b0363db5e0c27646f932676283b43d8" checksum = "2ce3498fe0aba81e62e477408383196b4b0363db5e0c27646f932676283b43d8"
dependencies = [ dependencies = [
"windows-core 0.62.0", "windows-core",
"windows-link 0.2.0", "windows-link 0.2.0",
] ]
[[package]]
name = "windows-result"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
dependencies = [
"windows-link 0.1.3",
]
[[package]] [[package]]
name = "windows-result" name = "windows-result"
version = "0.4.0" version = "0.4.0"
@@ -3005,15 +2700,6 @@ dependencies = [
"windows-link 0.2.0", "windows-link 0.2.0",
] ]
[[package]]
name = "windows-strings"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
dependencies = [
"windows-link 0.1.3",
]
[[package]] [[package]]
name = "windows-strings" name = "windows-strings"
version = "0.5.0" version = "0.5.0"
@@ -3107,15 +2793,6 @@ dependencies = [
"windows_x86_64_msvc 0.53.0", "windows_x86_64_msvc 0.53.0",
] ]
[[package]]
name = "windows-threading"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
dependencies = [
"windows-link 0.1.3",
]
[[package]] [[package]]
name = "windows-threading" name = "windows-threading"
version = "0.2.0" version = "0.2.0"
@@ -3322,16 +2999,6 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "winresource"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edcacf11b6f48dd21b9ba002f991bdd5de29b2da8cc2800412f4b80f677e4957"
dependencies = [
"toml",
"version_check",
]
[[package]] [[package]]
name = "wit-bindgen" name = "wit-bindgen"
version = "0.45.0" version = "0.45.0"
@@ -3463,12 +3130,6 @@ dependencies = [
"synstructure", "synstructure",
] ]
[[package]]
name = "zeroize"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
[[package]] [[package]]
name = "zerotrie" name = "zerotrie"
version = "0.2.2" version = "0.2.2"
+8 -6
View File
@@ -1,13 +1,15 @@
[package] [package]
name = "gta-tools" name = "gta-tools"
version = "0.7.0" version = "0.10.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 = [ catppuccin-egui = { version = "5.6.0", default-features = false, features = [
"egui32", "egui32",
] } ] }
chrono = { version = "0.4.41", default-features = false, features = ["clock"] }
eframe = { version = "0.32.2", default-features = false, features = [ eframe = { version = "0.32.2", default-features = false, features = [
"glow", "glow",
"default_fonts", "default_fonts",
@@ -16,18 +18,18 @@ egui_extras = { version = "0.32.2", default-features = false, features = [
"image", "image",
"svg", "svg",
] } ] }
humantime = "2.3.0"
image = { version = "0.25.8", default-features = false, features = ["png"] } image = { version = "0.25.8", default-features = false, features = ["png"] }
log = "0.4.28"
open = "5.3.2" open = "5.3.2"
semver = "1.0.26"
serde = { version = "1.0.219", features = ["derive"] } serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.143" serde_json = "1.0.143"
strum = { version = "0.27.2", features = ["derive"] } strum = { version = "0.27.2", features = ["derive"] }
sysinfo = "0.37.0"
ureq = { version = "3.1.0", features = ["json"] }
windows = { version = "0.62.0", features = [ windows = { version = "0.62.0", features = [
"Win32_NetworkManagement_WindowsFirewall", "Win32_NetworkManagement_WindowsFirewall",
"Win32_Security", "Win32_Security",
"Win32_System_Com", "Win32_System_Com",
"Win32_System_Diagnostics_ToolHelp",
"Win32_System_Threading", "Win32_System_Threading",
"Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Input_KeyboardAndMouse",
"Win32_UI_Shell", "Win32_UI_Shell",
@@ -37,4 +39,4 @@ winreg = "0.55.0"
[build-dependencies] [build-dependencies]
static_vcruntime = "2.0.0" static_vcruntime = "2.0.0"
winresource = "0.1.23" tauri-winres = "0.3.3"
+14 -5
View File
@@ -2,12 +2,15 @@
A toolset of convenient things for GTA V Online. A toolset of convenient things for GTA V Online.
![](https://i.vgy.me/aQ1qgw.png) ![](https://i.vgy.me/OrzaR8.png) <picture>
<source srcset="https://i.vgy.me/M4sOHh.png" media="(prefers-color-scheme: dark)">
<img src="https://i.vgy.me/mpO9uc.png">
</picture>
## 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 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**.
@@ -16,15 +19,15 @@ You will need the Rust toolchain, which can be obtained [here](https://rustup.rs
Every feature of GTA Tools is Legacy/Enhanced-agnostic. Some functionality of GTA Tools requires administrator access. If necessary, GTA Tools can either be started as admin manually, or, the user can simply use the <kbd>Elevate</kbd> button to relaunch GTA Tools as admin. Every feature of GTA Tools is Legacy/Enhanced-agnostic. Some functionality of GTA Tools requires administrator access. If necessary, GTA Tools can either be started as admin manually, or, the user can simply use the <kbd>Elevate</kbd> button to relaunch GTA Tools as admin.
It is recommended to use an up-to-date version of GTA Tools from [releases](https://codeberg.org/futile/gta-tools/releases). You can check for updates to your existing installation by going to the <kbd>About</kbd> page of GTA Tools and hovering the <kbd>↓</kbd> button near the version number. It is recommended to always use an up-to-date version of GTA Tools from [releases](https://github.com/elituf/gta-tools/releases). You can also easily access this repository by going to the <kbd>About</kbd> page of GTA Tools and clicking the GitHub button beside the version number.
#### Game #### Game
This section is quite simple. This section is quite simple.
There is a <kbd>Launch</kbd>[^1] 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
@@ -56,5 +59,11 @@ 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
- 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.
[^1]: Otherwise, if you are, for example, using those keys to fly a plane, <kbd>Anti AFK</kbd> activating could cancel the input. [^1]: Otherwise, if you are, for example, using those keys to fly a plane, <kbd>Anti AFK</kbd> activating could cancel the input.
[^2]: Specifically, the Windows mouse cursor, not the in-game mouse cursor. This check is mostly used to prevent typing while you are in the Rockstar overlay. [^2]: Specifically, the Windows mouse cursor, not the in-game mouse cursor. This check is mostly used to prevent typing while you are in the Rockstar overlay.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+17 -1
View File
@@ -1,7 +1,23 @@
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")
.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");
} }
-6
View File
@@ -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
+1 -1
View File
@@ -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,
+20 -21
View File
@@ -1,11 +1,11 @@
use crate::util::{ use crate::util::{
consts::game::{EXE_ENHANCED, EXE_LEGACY}, consts::game::{EXE_ENHANCED, EXE_LEGACY},
countdown::Countdown, countdown::Countdown,
system_info::SystemInfo,
}; };
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use sysinfo::System;
use windows::Win32::{ use windows::Win32::{
Foundation::{CloseHandle, HANDLE, NTSTATUS}, Foundation::{HANDLE, NTSTATUS},
System::Threading::{OpenProcess, PROCESS_SUSPEND_RESUME}, System::Threading::{OpenProcess, PROCESS_SUSPEND_RESUME},
}; };
@@ -48,34 +48,33 @@ unsafe extern "system" {
unsafe fn NtResumeProcess(ProcessHandle: HANDLE) -> NTSTATUS; unsafe fn NtResumeProcess(ProcessHandle: HANDLE) -> NTSTATUS;
} }
fn get_gta_pid(sysinfo: &mut System) -> u32 { fn get_gta_pid(system_info: &mut SystemInfo) -> Option<u32> {
sysinfo.refresh_all(); system_info.refresh();
if let Some((pid, _)) = sysinfo system_info
.processes() .processes()
.iter() .iter()
.find(|(_, p)| p.name() == EXE_ENHANCED || p.name() == EXE_LEGACY) .find(|p| p.name() == EXE_ENHANCED || p.name() == EXE_LEGACY)
{ .map(|p| p.pid())
return pid.as_u32();
}
u32::MAX
} }
pub fn activate(game_handle: &mut HANDLE, sysinfo: &mut System) { pub fn activate(game_handle: &mut HANDLE, system_info: &mut SystemInfo) -> bool {
let pid = get_gta_pid(sysinfo); let Some(pid) = get_gta_pid(system_info) else {
if pid == u32::MAX { return false;
return; };
match unsafe { OpenProcess(PROCESS_SUSPEND_RESUME, false, pid) } {
Ok(handle) => *game_handle = handle,
Err(why) => {
log::error!("failed to suspend game for empty session:\n{why}");
return false;
} }
unsafe {
*game_handle = OpenProcess(PROCESS_SUSPEND_RESUME, false, pid).unwrap();
let _ = NtSuspendProcess(*game_handle);
} }
unsafe { NtSuspendProcess(*game_handle) }.unwrap();
true
} }
pub fn deactivate(game_handle: &mut HANDLE) { pub fn deactivate(game_handle: &mut HANDLE) {
unsafe {
if !game_handle.is_invalid() { if !game_handle.is_invalid() {
let _ = NtResumeProcess(*game_handle); // ignoring the return because this function behaves very weirdly
let _ = CloseHandle(*game_handle); let _ = unsafe { NtResumeProcess(*game_handle) };
}
} }
} }
+15 -11
View File
@@ -1,6 +1,8 @@
use crate::util::consts::game::{EXE_ENHANCED, EXE_LEGACY}; use crate::util::{
consts::game::{EXE_ENHANCED, EXE_LEGACY},
system_info::SystemInfo,
};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use sysinfo::System;
const INTERVAL: Duration = Duration::from_secs(3); const INTERVAL: Duration = Duration::from_secs(3);
@@ -24,7 +26,7 @@ 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, system_info: &mut SystemInfo) {
if force_close_button_clicked && !self.counting { if force_close_button_clicked && !self.counting {
self.button_text = "Are you sure?".to_owned(); self.button_text = "Are you sure?".to_owned();
self.timer = Instant::now(); self.timer = Instant::now();
@@ -34,7 +36,7 @@ impl ForceClose {
if self.counting && self.timer.elapsed() >= INTERVAL { if self.counting && self.timer.elapsed() >= INTERVAL {
self.reset(); self.reset();
} else if force_close_button_clicked && !self.current_frame { } else if force_close_button_clicked && !self.current_frame {
activate(sysinfo); activate(system_info);
self.reset(); self.reset();
} }
self.finish_current_frame(); self.finish_current_frame();
@@ -51,14 +53,16 @@ impl ForceClose {
} }
} }
fn activate(sysinfo: &mut System) { fn activate(system_info: &mut SystemInfo) {
sysinfo.refresh_all(); system_info.refresh();
sysinfo system_info
.processes() .processes()
.iter() .iter()
.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| {
p.kill(); if !p.kill() {
log::error!("failed to force close game, probably due to access denied");
}
}); });
sysinfo.refresh_all(); system_info.refresh();
} }
+31 -37
View File
@@ -1,10 +1,12 @@
use crate::util::consts::game::{EXE_ENHANCED, EXE_LEGACY}; use crate::util::{
consts::game::{EXE_ENHANCED, EXE_LEGACY},
system_info::SystemInfo,
};
use std::{ use std::{
path::Path, path::Path,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use strum::{Display, EnumIter}; use strum::{Display, EnumIter};
use sysinfo::System;
use windows::{ use windows::{
Win32::{ Win32::{
NetworkManagement::WindowsFirewall::{ NetworkManagement::WindowsFirewall::{
@@ -62,59 +64,55 @@ impl Default for GameNetworking {
impl Drop for GameNetworking { impl Drop for GameNetworking {
fn drop(&mut self) { fn drop(&mut self) {
unsafe {
if self.com_initialized { if self.com_initialized {
CoUninitialize(); unsafe { CoUninitialize() };
}
} }
} }
} }
impl GameNetworking { impl GameNetworking {
pub fn block_all(&mut self, sysinfo: &mut System) { pub fn block_all(&mut self, system_info: &mut SystemInfo) {
let Some(exe_path) = get_game_exe_path(sysinfo) else { let Some(exe_path) = get_game_exe_path(system_info) else {
self.blocked_status = BlockedStatus::Failed; self.blocked_status = BlockedStatus::Failed;
return; return;
}; };
let policy: INetFwPolicy2 = let policy: INetFwPolicy2 =
unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER).unwrap() }; unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER) }.unwrap();
let rules = unsafe { policy.Rules().unwrap() }; let rules = unsafe { policy.Rules() }.unwrap();
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 [ for filter in [
(FILTER_NAME_IN, NET_FW_RULE_DIR_IN), (FILTER_NAME_IN, NET_FW_RULE_DIR_IN),
(FILTER_NAME_OUT, NET_FW_RULE_DIR_OUT), (FILTER_NAME_OUT, NET_FW_RULE_DIR_OUT),
] { ] {
let _ = unsafe { rules.Remove(&BSTR::from(filter.0)) }; unsafe { rules.Remove(&BSTR::from(filter.0)) }.unwrap();
unsafe {
let rule: INetFwRule = let rule: INetFwRule =
CoCreateInstance(&NetFwRule, None, CLSCTX_INPROC_SERVER).unwrap(); unsafe { CoCreateInstance(&NetFwRule, None, CLSCTX_INPROC_SERVER) }.unwrap();
rule.SetName(&BSTR::from(filter.0)).unwrap(); unsafe { rule.SetName(&BSTR::from(filter.0)) }.unwrap();
rule.SetApplicationName(&exe_path).unwrap(); unsafe { rule.SetApplicationName(&exe_path) }.unwrap();
rule.SetDirection(filter.1).unwrap(); unsafe { rule.SetDirection(filter.1) }.unwrap();
rule.SetEnabled(true.into()).unwrap(); unsafe { rule.SetEnabled(true.into()) }.unwrap();
rule.SetAction(NET_FW_ACTION_BLOCK).unwrap(); unsafe { rule.SetAction(NET_FW_ACTION_BLOCK) }.unwrap();
rule.SetProtocol(NET_FW_IP_PROTOCOL_ANY.0).unwrap(); unsafe { rule.SetProtocol(NET_FW_IP_PROTOCOL_ANY.0) }.unwrap();
rules.Add(&rule).unwrap(); unsafe { rules.Add(&rule) }.unwrap();
}
} }
self.blocked_status = Self::is_blocked().into(); self.blocked_status = Self::is_blocked().into();
} }
pub fn unblock_all(&mut self) { pub fn unblock_all(&mut self) {
let policy: INetFwPolicy2 = let policy: INetFwPolicy2 =
unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER).unwrap() }; unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER) }.unwrap();
let rules = unsafe { policy.Rules().unwrap() }; let rules = unsafe { policy.Rules() }.unwrap();
unsafe { rules.Remove(&BSTR::from(FILTER_NAME_IN)).unwrap() }; unsafe { rules.Remove(&BSTR::from(FILTER_NAME_IN)) }.unwrap();
unsafe { rules.Remove(&BSTR::from(FILTER_NAME_OUT)).unwrap() }; unsafe { rules.Remove(&BSTR::from(FILTER_NAME_OUT)) }.unwrap();
self.blocked_status = Self::is_blocked().into(); self.blocked_status = Self::is_blocked().into();
} }
fn is_blocked() -> bool { fn is_blocked() -> bool {
let policy: INetFwPolicy2 = let policy: INetFwPolicy2 =
unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER).unwrap() }; unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER) }.unwrap();
let rules = unsafe { policy.Rules().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
} }
@@ -133,15 +131,11 @@ impl GameNetworking {
} }
} }
fn get_game_exe_path(sysinfo: &mut System) -> Option<&Path> { fn get_game_exe_path(system_info: &mut SystemInfo) -> Option<&Path> {
sysinfo.refresh_all(); system_info.refresh();
if let Some((_, process)) = sysinfo system_info
.processes() .processes()
.iter() .iter()
.find(|(_, p)| p.name() == EXE_ENHANCED || p.name() == EXE_LEGACY) .find(|p| p.name() == EXE_ENHANCED || p.name() == EXE_LEGACY)
{ .and_then(|p| p.exe())
process.exe()
} else {
None
}
} }
+14 -12
View File
@@ -22,19 +22,17 @@ 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_v_enhanced) = hklm.open_subkey(rockstar_url) else {
return; return;
@@ -46,15 +44,19 @@ pub fn launch(platform: &Platform, version: &LaunchVersion) {
}; };
let mut play_gtav_path = PathBuf::from(install_folder); let mut play_gtav_path = PathBuf::from(install_folder);
play_gtav_path.push("PlayGTAV.exe"); play_gtav_path.push("PlayGTAV.exe");
// ignoring the return because if it errors that means GTA isn't installed via Rockstar
let _ = Command::new(play_gtav_path).spawn(); let _ = Command::new(play_gtav_path).spawn();
} }
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
View File
@@ -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;
+32 -29
View File
@@ -1,7 +1,12 @@
use crate::{ use crate::{
features, features,
gui::{colours, settings::Settings, tools, ui_ext::UiExt}, gui::{settings::Settings, tools, ui_ext::UiExt},
util::{consts::game::WINDOW_TITLE, meta::Meta, persistent_state::PersistentState, win}, util::{
consts::{colours, game::WINDOW_TITLE},
persistent_state::PersistentState,
system_info::SystemInfo,
win,
},
}; };
use eframe::egui; use eframe::egui;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
@@ -36,11 +41,10 @@ impl Default for Flags {
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct App { pub struct App {
pub meta: Meta,
pub settings: Settings, pub settings: Settings,
stage: Stage, stage: Stage,
pub flags: Flags, pub flags: Flags,
pub sysinfo: sysinfo::System, pub system_info: SystemInfo,
game_handle: windows::Win32::Foundation::HANDLE, game_handle: windows::Win32::Foundation::HANDLE,
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,
@@ -105,21 +109,25 @@ impl App {
}); });
}); });
let force_close_button = ui.add_sized( let force_close_button = ui.add_sized(
[104.0, 0.0], [108.0, 0.0],
egui::Button::new(&self.force_close.button_text), egui::Button::new(&self.force_close.button_text),
); );
self.force_close self.force_close
.prompt(force_close_button.clicked(), &mut self.sysinfo); .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, _ctx: &egui::Context, 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(
&mut self.game_handle,
&mut self.system_info,
)
{
self.empty_session.interval = Instant::now(); self.empty_session.interval = Instant::now();
self.empty_session.disabled = true; self.empty_session.disabled = true;
features::empty_session::activate(&mut self.game_handle, &mut self.sysinfo);
} }
ui.label(&self.empty_session.countdown.i_string); ui.label(&self.empty_session.countdown.i_string);
}); });
@@ -167,7 +175,7 @@ impl App {
.add_sized([button_width, 18.0], egui::Button::new("Block")) .add_sized([button_width, 18.0], egui::Button::new("Block"))
.clicked() .clicked()
{ {
self.game_networking.block_all(&mut self.sysinfo); self.game_networking.block_all(&mut self.system_info);
} }
if ui if ui
.add_sized([button_width, 18.0], egui::Button::new("Unblock")) .add_sized([button_width, 18.0], egui::Button::new("Unblock"))
@@ -219,31 +227,25 @@ impl App {
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.style_mut().visuals.hyperlink_color = colours::RED; ui.scope(|ui| {
ui.hyperlink_to("", "https://codeberg.org/futile/gta-tools"); ui.style_mut().visuals.override_text_color = Some(colours::RED);
ui.label("");
});
ui.label(" from "); ui.label(" from ");
ui.style_mut().visuals.hyperlink_color =
catppuccin_egui::Theme::from(self.settings.theme).text;
ui.hyperlink_to("futile", "https://futile.eu"); ui.hyperlink_to("futile", "https://futile.eu");
}); });
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
ui.label(format!( ui.style_mut().spacing.button_padding = egui::Vec2::new(4.0, 0.0);
"v{} {}", if ui.button("").on_hover_text("View source code").clicked() {
self.meta.current_version, open::that("https://github.com/elituf/gta-tools").unwrap();
if cfg!(debug_assertions) { "(dev)" } else { "" }
));
let button = ui.add_enabled_ui(self.meta.newer_version_available, |ui| {
ui.style_mut().spacing.button_padding = egui::Vec2::new(3.0, 0.0);
ui.button("")
.on_disabled_hover_text("Already up to date.")
.on_hover_text(format!(
"New version available! ({})",
self.meta.latest_release.version
))
});
if button.inner.clicked() {
open::that(&self.meta.latest_release.download_url).unwrap();
} }
if cfg!(debug_assertions) {
ui.label("(dev)");
}
ui.label(format!("v{}", env!("CARGO_PKG_VERSION")))
.on_hover_text(
egui::RichText::new(env!("LATEST_GIT_COMMIT_HASH")).monospace(),
);
}); });
}); });
ui.add(egui::Image::new(egui::include_image!( ui.add(egui::Image::new(egui::include_image!(
@@ -258,6 +260,7 @@ 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();
-16
View File
@@ -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,
}
}
}
+10 -21
View File
@@ -17,19 +17,14 @@ impl App {
if ui.button("open storage path").clicked() { if ui.button("open storage path").clicked() {
open::that_detached(path::APP_STORAGE.as_path()).unwrap(); open::that_detached(path::APP_STORAGE.as_path()).unwrap();
} }
ui.checkbox(
&mut self.meta.newer_version_available,
"spoof new version available",
)
.on_hover_text("(this could already be checked if\nthere actually IS a new version)");
ui.scope(|ui| { 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}\""));
@@ -42,10 +37,7 @@ impl App {
ui.build_menu(&mut self.game_networking.blocked_status); ui.build_menu(&mut self.game_networking.blocked_status);
}); });
}); });
if ui if ui.add(egui::Button::new("force refresh theme")).clicked() {
.add(egui::Button::new("force refresh theme"))
.clicked()
{
catppuccin_egui::set_theme(ui.ctx(), self.settings.theme.into()); catppuccin_egui::set_theme(ui.ctx(), self.settings.theme.into());
} }
}); });
@@ -56,19 +48,16 @@ impl App {
)); ));
ui.label(format!("can activate: {}", self.anti_afk.can_activate())); ui.label(format!("can activate: {}", self.anti_afk.can_activate()));
}); });
ui.collapsing("sysinfo", |ui| { ui.collapsing("system info", |ui| {
if ui.button("refresh all").clicked() { if ui.button("refresh").clicked() {
self.sysinfo.refresh_all(); self.system_info.refresh();
} }
let pid = self let pid = self
.sysinfo .system_info
.processes() .processes()
.iter() .iter()
.find(|(_, p)| p.name() == EXE_ENHANCED || p.name() == EXE_LEGACY) .find(|p| p.name() == EXE_ENHANCED || p.name() == EXE_LEGACY)
.map_or_else( .map_or_else(|| "no pid found!".to_owned(), |p| p.pid().to_string());
|| "no pid found!".to_owned(),
|(pid, _)| pid.as_u32().to_string(),
);
ui.label(format!("gta pid: {pid}")); ui.label(format!("gta pid: {pid}"));
}); });
ui.collapsing("app state", |ui| ui.label(format!("{self:#?}"))); ui.collapsing("app state", |ui| ui.label(format!("{self:#?}")));
+17 -8
View File
@@ -14,15 +14,14 @@ fn app_creator(
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 // check if we're elevated. if not, and the user wants an elevated launch - relaunch elevated
if !app.flags.elevated && app.settings.start_elevated { if !app.flags.elevated && app.settings.start_elevated {
win::elevate(win::ElevationExitMethod::Forced); win::elevate(win::ElevationExitMethod::Forced);
} }
// refresh sysinfo because it initializes with nothing // refresh system info because it initializes with nothing
app.sysinfo.refresh_all(); 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
@@ -33,16 +32,26 @@ fn app_creator(
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;
}); });
// 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
+1 -1
View File
@@ -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());
}); });
+8 -7
View File
@@ -1,26 +1,27 @@
#![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;
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(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 {
message += &format!("\nstack backtrace:\n{backtrace}");
}
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 -3
View File
@@ -1,7 +1,6 @@
mod codeberg;
pub mod consts; pub mod consts;
pub mod countdown; pub mod countdown;
pub mod log; pub mod logging;
pub mod meta;
pub mod persistent_state; pub mod persistent_state;
pub mod system_info;
pub mod win; pub mod win;
-50
View File
@@ -1,50 +0,0 @@
use semver::Version;
use serde::Deserialize;
use std::time::Duration;
const APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
const CODEBERG_ENDPOINT_ROOT: &str = "https://codeberg.org/api/v1";
#[derive(Debug)]
pub struct Release {
pub version: Version,
pub download_url: String,
}
impl Default for Release {
fn default() -> Self {
Self {
version: Version::new(0, 0, 0),
download_url: String::new(),
}
}
}
#[derive(Deserialize)]
struct LatestRelease {
tag_name: String,
assets: Vec<Asset>,
}
#[derive(Deserialize)]
struct Asset {
browser_download_url: String,
}
pub fn get_latest_release() -> Result<Release, Box<dyn std::error::Error>> {
let request_url = format!("{CODEBERG_ENDPOINT_ROOT}/repos/futile/gta-tools/releases/latest");
let mut response = ureq::get(request_url)
.config()
.timeout_global(Some(Duration::from_secs(10)))
.user_agent(APP_USER_AGENT)
.build()
.call()?;
let body = response.body_mut();
let json = body.read_json::<LatestRelease>()?;
let tag_name = json.tag_name;
let browser_download_url = &json.assets[0].browser_download_url;
Ok(Release {
version: Version::parse(&tag_name)?,
download_url: browser_download_url.to_owned(),
})
}
+19
View File
@@ -13,3 +13,22 @@ 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 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,
}
}
}
}
-20
View File
@@ -1,20 +0,0 @@
use crate::util::consts::path;
use std::{fs::File, io::Write};
use strum::Display;
#[derive(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();
}
+64
View File
@@ -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();
write!(
file,
"[{}][{}]\n{}\n\n",
humantime::format_rfc3339_seconds(SystemTime::now()),
record.level(),
record.args()
)
.unwrap();
file.flush().unwrap();
}
fn log_to_stderr(&self, record: &log::Record) {
eprintln!(
"[{}][{}]\n{}",
humantime::format_rfc3339_seconds(SystemTime::now()),
record.level(),
record.args()
)
}
}
-29
View File
@@ -1,29 +0,0 @@
use crate::{util::codeberg, util::log};
use semver::Version;
#[derive(Debug)]
pub struct Meta {
pub current_version: Version,
pub latest_release: codeberg::Release,
pub newer_version_available: bool,
}
impl Default for Meta {
fn default() -> Self {
let current_version = Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
let latest_release = codeberg::get_latest_release().unwrap_or_else(|why| {
let message = format!("failed to get latest codeberg release:\n{why}");
log::log(log::LogLevel::Error, &message);
codeberg::Release::default()
});
let newer_version_available = matches!(
&current_version.cmp_precedence(&latest_release.version),
std::cmp::Ordering::Less
);
Self {
current_version,
latest_release,
newer_version_available,
}
}
}
+17 -1
View File
@@ -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;
}
} }
+117
View File
@@ -0,0 +1,117 @@
use std::{
ffi::{OsStr, OsString},
os::windows::{ffi::OsStringExt, process::CommandExt},
path::{Path, PathBuf},
process::Command,
};
use windows::{
Win32::System::{
Diagnostics::ToolHelp::{
CreateToolhelp32Snapshot, PROCESSENTRY32W, Process32FirstW, Process32NextW,
TH32CS_SNAPPROCESS,
},
Threading::{
CREATE_NO_WINDOW, OpenProcess, PROCESS_NAME_WIN32, PROCESS_QUERY_LIMITED_INFORMATION,
QueryFullProcessImageNameW,
},
},
core::PWSTR,
};
#[derive(Clone, Debug)]
pub struct Process {
pid: u32,
name: OsString,
exe: Option<PathBuf>,
}
impl Process {
pub const fn pid(&self) -> u32 {
self.pid
}
pub fn name(&self) -> &OsStr {
&self.name
}
pub fn exe(&self) -> Option<&Path> {
self.exe.as_deref()
}
pub fn kill(&self) -> bool {
let mut taskkill = Command::new("taskkill.exe");
taskkill.creation_flags(CREATE_NO_WINDOW.0);
taskkill.arg("/F").arg("/PID").arg(self.pid.to_string());
match taskkill.output() {
Ok(output) => output.status.success(),
Err(_) => false,
}
}
}
#[derive(Debug, Default)]
pub struct SystemInfo {
processes: Vec<Process>,
}
impl SystemInfo {
pub fn refresh(&mut self) {
let mut processes = Vec::new();
let snapshot_handle = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) }.unwrap();
let mut process_entry = PROCESSENTRY32W {
dwSize: size_of::<PROCESSENTRY32W>() as u32,
..Default::default()
};
unsafe { Process32FirstW(snapshot_handle, &raw mut process_entry) }.unwrap();
let exe_full_path = get_exe_full_path(&process_entry);
processes.push(Process {
pid: process_entry.th32ProcessID,
name: wide_array_to_os_string(&process_entry.szExeFile),
exe: exe_full_path,
});
while unsafe { Process32NextW(snapshot_handle, &raw mut process_entry) }.is_ok() {
let exe_full_path = get_exe_full_path(&process_entry);
processes.push(Process {
pid: process_entry.th32ProcessID,
name: wide_array_to_os_string(&process_entry.szExeFile),
exe: exe_full_path,
});
}
self.processes = processes;
}
pub fn processes(&self) -> &[Process] {
&self.processes
}
}
fn get_exe_full_path(process_entry: &PROCESSENTRY32W) -> Option<PathBuf> {
let process_handle_result = unsafe {
OpenProcess(
PROCESS_QUERY_LIMITED_INFORMATION,
false,
process_entry.th32ProcessID,
)
};
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 {
QueryFullProcessImageNameW(
process_handle,
PROCESS_NAME_WIN32,
PWSTR(exe_name.as_mut_ptr()),
&raw mut dw_size,
)
};
match image_name_result {
Ok(()) => Some(PathBuf::from(wide_array_to_os_string(&exe_name))),
Err(_) => None,
}
})
}
fn wide_array_to_os_string(wide: &[u16]) -> OsString {
let null_pos = wide.iter().position(|&x| x == 0).unwrap_or(wide.len());
OsString::from_wide(&wide[..null_pos])
}
+11 -15
View File
@@ -25,25 +25,21 @@ pub fn is_cursor_visible() -> bool {
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(&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 elevate(closing: ElevationExitMethod) {
@@ -66,22 +62,22 @@ pub fn elevate(closing: ElevationExitMethod) {
pub fn is_elevated() -> bool { pub fn is_elevated() -> bool {
let mut token: HANDLE = HANDLE::default(); let mut token: HANDLE = HANDLE::default();
unsafe { if unsafe { OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &raw mut token) }.is_err() {
if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token).is_err() {
return false; return false;
} }
let mut elevation = TOKEN_ELEVATION::default(); let mut elevation = TOKEN_ELEVATION::default();
let mut size = u32::try_from(std::mem::size_of::<TOKEN_ELEVATION>()).unwrap(); let mut size = u32::try_from(std::mem::size_of::<TOKEN_ELEVATION>()).unwrap();
let result = GetTokenInformation( let result = unsafe {
GetTokenInformation(
token, token,
TokenElevation, TokenElevation,
Some((&raw mut elevation).cast()), Some((&raw mut elevation).cast()),
size, size,
&mut size, &raw mut size,
); )
CloseHandle(token).unwrap(); };
unsafe { CloseHandle(token) }.unwrap();
result.is_ok() && elevation.TokenIsElevated != 0 result.is_ok() && elevation.TokenIsElevated != 0
}
} }
pub fn is_system_theme_dark() -> bool { pub fn is_system_theme_dark() -> bool {