feat(overlay): damage logger, menu, and fps unlocker

credit to [kitkat](https://github.com/kitkat-multiverse/Starlight-Patch/commit/a968b280d410cc44dcdbc92feda82174674d41b3)
This commit is contained in:
capyb2222 committed 2026-10-08 17:41:42 -07:00
1 parent 2af3510615
commit 696e1c0b9a
10 files changed
+1521 -5

No files matched your search

-1
View File
@@ -1,3 +1,2 @@
.idea
/target
Cargo.lock
Generated
+756
View File
@@ -0,0 +1,756 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aho-corasick"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
dependencies = [
"memchr",
]
[[package]]
name = "anyhow"
version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
[[package]]
name = "cc"
version = "1.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3eb0f42d6c360dc3f8a821f6bf2fdea7f72bfd36b3076eb0e6d1e9e0752fff4"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
[[package]]
name = "chlorine"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c00d31b1d19317b4777ec879192d3745bd97d05262b4b19cb1dda284b9d22f19"
[[package]]
name = "find-msvc-tools"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d"
[[package]]
name = "hk4e-patch"
version = "0.1.0"
dependencies = [
"anyhow",
"cc",
"hudhook",
"ilhook",
"lazy_static",
"libloading",
"patternscan",
"serde_json",
"winapi",
"windows 0.54.0",
]
[[package]]
name = "hudhook"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d7f3def5b8fafd0bd3acf6a63fdbe5c637963ce1d207192ef3c47bd4f28b394"
dependencies = [
"bitflags 2.13.2",
"cc",
"imgui",
"once_cell",
"parking_lot",
"tracing",
"tracing-subscriber",
"windows 0.62.2",
"windows-numerics",
]
[[package]]
name = "iced-x86"
version = "1.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c447cff8c7f384a7d4f741cfcff32f75f3ad02b406432e8d6c878d56b1edf6b"
dependencies = [
"lazy_static",
]
[[package]]
name = "ilhook"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c40cf69be52a2e8e2a375673ecfde90b7790aa9d8aa55dd463dc7e8faacb89c9"
dependencies = [
"bitflags 2.13.2",
"iced-x86",
"libc",
"regex",
"thiserror",
"windows-sys",
]
[[package]]
name = "imgui"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8addafa5cecf0515812226e806913814e02ce38d10215778082af5174abe5669"
dependencies = [
"bitflags 1.3.2",
"cfg-if",
"imgui-sys",
"mint",
"parking_lot",
]
[[package]]
name = "imgui-sys"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ead193f9f4b60398e8b8f4ab1483e2321640d87aeebdaa3e5f44c55633ccd804"
dependencies = [
"cc",
"cfg-if",
"chlorine",
"mint",
]
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
[[package]]
name = "libloading"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
dependencies = [
"cfg-if",
"windows-link",
]
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
[[package]]
name = "matchers"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
dependencies = [
"regex-automata",
]
[[package]]
name = "memchr"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "mint"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff"
[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "parking_lot"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-link",
]
[[package]]
name = "patternscan"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbf9ac94ae7c3d7f743ec57e0b6b05077631c1a90c6cea9b162a69efa6d6bbde"
[[package]]
name = "pin-project-lite"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "proc-macro2"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags 2.13.2",
]
[[package]]
name = "regex"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
dependencies = [
"serde_core",
]
[[package]]
name = "serde_core"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.6",
]
[[package]]
name = "serde_json"
version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "shlex"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "smallvec"
version = "1.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e"
[[package]]
name = "syn"
version = "2.0.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "thread_local"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070"
dependencies = [
"cfg-if",
]
[[package]]
name = "tracing"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [
"log",
"pin-project-lite",
"tracing-core",
]
[[package]]
name = "tracing-core"
version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
dependencies = [
"once_cell",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
dependencies = [
"matchers",
"once_cell",
"regex-automata",
"thread_local",
"tracing",
"tracing-core",
]
[[package]]
name = "unicode-ident"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.54.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49"
dependencies = [
"windows-core 0.54.0",
"windows-targets 0.52.6",
]
[[package]]
name = "windows"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
dependencies = [
"windows-collections",
"windows-core 0.62.2",
"windows-future",
"windows-numerics",
]
[[package]]
name = "windows-collections"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
dependencies = [
"windows-core 0.62.2",
]
[[package]]
name = "windows-core"
version = "0.54.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65"
dependencies = [
"windows-result 0.1.2",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-core"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link",
"windows-result 0.4.1",
"windows-strings",
]
[[package]]
name = "windows-future"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
dependencies = [
"windows-core 0.62.2",
"windows-link",
"windows-threading",
]
[[package]]
name = "windows-implement"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "windows-interface"
version = "0.59.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-numerics"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
dependencies = [
"windows-core 0.62.2",
"windows-link",
]
[[package]]
name = "windows-result"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets 0.53.5",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm 0.52.6",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.53.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link",
"windows_aarch64_gnullvm 0.53.1",
"windows_aarch64_msvc 0.53.1",
"windows_i686_gnu 0.53.1",
"windows_i686_gnullvm 0.53.1",
"windows_i686_msvc 0.53.1",
"windows_x86_64_gnu 0.53.1",
"windows_x86_64_gnullvm 0.53.1",
"windows_x86_64_msvc 0.53.1",
]
[[package]]
name = "windows-threading"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
dependencies = [
"windows-link",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_i686_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]]
name = "zmij"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
+3
View File
@@ -20,8 +20,11 @@ windows = { version = "0.54.0", features = [
"Win32_System_Console",
"Win32_System_Threading",
"Win32_System_Memory",
"Win32_UI_Input_KeyboardAndMouse",
] }
winapi = { version = "0.3",features = ["winnt","libloaderapi","memoryapi","winuser"]}
hudhook = { version = "0.9", default-features = false, features = ["dx11"] }
serde_json = "1"
[build-dependencies]
cc = "1.0.90"
+7 -2
View File
@@ -1,6 +1,11 @@
# hk4e-patch-universal
Genshin Impact encryption patch that should work on 6.5 and forward for a while
Genshin Impact encryption patch made mainly for [LunaGC_7.1.0](https://github.com/capyb2222/LunaGC_7.1.0). The patch should also work with other Grasscutter-based servers.
## Features
- Damage logger, toggled with `Ctrl+K`
- Menu, opened with `Ctrl+L`
- FPS unlocker (adjustable)
## Credits
[xeondev](https://git.xeondev.com/reversedrooms/hk4e-patch) for the original patch and [oureveryday](https://github.com/oureveryday/) for the original hk4e-patch-universal
[xeondev](https://git.xeondev.com/reversedrooms/hk4e-patch) for the original patch, [oureveryday](https://github.com/oureveryday/) for the original hk4e-patch-universal, and [kitkat](https://github.com/kitkat-multiverse/Starlight-Patch) for the base patch and the FPS unlocker.
+50
View File
@@ -0,0 +1,50 @@
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
use serde_json::{json, Value};
const FILE_NAME: &str = "lunagc-patch.json";
const DEFAULT_FPS: i32 = 120;
pub static SHOW_PANEL: AtomicBool = AtomicBool::new(true);
pub static FPS_ENABLED: AtomicBool = AtomicBool::new(true);
pub static FPS_TARGET: AtomicI32 = AtomicI32::new(DEFAULT_FPS);
fn path() -> Option<PathBuf> {
let mut path = std::env::current_exe().ok()?;
path.pop();
Some(path.join(FILE_NAME))
}
pub fn load() {
let Some(text) = path().and_then(|p| std::fs::read_to_string(p).ok()) else {
return;
};
let Ok(value) = serde_json::from_str::<Value>(&text) else {
return;
};
if let Some(show) = value["showDamagePanel"].as_bool() {
SHOW_PANEL.store(show, Ordering::Relaxed);
}
if let Some(enabled) = value["fpsUnlock"].as_bool() {
FPS_ENABLED.store(enabled, Ordering::Relaxed);
}
if let Some(target) = value["fpsTarget"].as_i64() {
FPS_TARGET.store(target.clamp(0, 1000) as i32, Ordering::Relaxed);
}
}
pub fn save() {
let value = json!({
"showDamagePanel": SHOW_PANEL.load(Ordering::Relaxed),
"fpsUnlock": FPS_ENABLED.load(Ordering::Relaxed),
"fpsTarget": FPS_TARGET.load(Ordering::Relaxed),
});
if let (Some(path), Ok(text)) = (path(), serde_json::to_string_pretty(&value)) {
if let Err(e) = std::fs::write(&path, text) {
crate::plog!("Failed to save {}: {e}", path.display());
}
}
}
+13 -2
View File
@@ -1,5 +1,6 @@
#![feature(str_from_utf16_endian)]
use std::sync::atomic::{AtomicUsize, Ordering};
use std::{sync::RwLock};
use lazy_static::lazy_static;
@@ -10,14 +11,16 @@ use windows::Win32::{Foundation::HINSTANCE, System::LibraryLoader::GetModuleFile
use std::ffi::CStr;
use std::path::Path;
mod config;
mod log;
mod proxy;
mod interceptor;
mod marshal;
mod modules;
mod overlay;
mod util;
use crate::modules::{Http, MhyContext, ModuleManager, Security, WinHttp};
use crate::modules::{Fps, Http, MhyContext, ModuleManager, Security, WinHttp};
unsafe fn thread_func() {
let mut module_manager = MODULE_MANAGER.write().unwrap();
@@ -52,17 +55,25 @@ unsafe fn thread_func() {
// the account sdk uses winhttp, not the C# path above
module_manager.enable(MhyContext::<WinHttp>::new(&exe_name));
config::load();
module_manager.enable(MhyContext::<Fps>::new(&exe_name));
crate::plog!("Successfully initialized!");
overlay::start(MODULE.load(Ordering::Relaxed));
}
static MODULE: AtomicUsize = AtomicUsize::new(0);
lazy_static! {
static ref MODULE_MANAGER: RwLock<ModuleManager> = RwLock::new(ModuleManager::default());
}
#[no_mangle]
#[allow(non_snake_case)]
unsafe extern "system" fn DllMain(_: HINSTANCE, call_reason: u32, _: *mut ()) -> bool {
unsafe extern "system" fn DllMain(module: HINSTANCE, call_reason: u32, _: *mut ()) -> bool {
if call_reason == DLL_PROCESS_ATTACH {
MODULE.store(module.0 as usize, Ordering::Relaxed);
log::start_session();
// here, not on the thread: the game may call the exports once DllMain returns
+78
View File
@@ -0,0 +1,78 @@
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use super::{MhyContext, MhyModule, ModuleType};
use crate::config::{FPS_ENABLED, FPS_TARGET};
use crate::util;
use anyhow::Result;
use ilhook::x64::Registers;
const SET_TARGET_FPS: &str = "E8 ? ? ? ? E8 ? ? ? ? 83 F8 1F 0F 9C 05";
const GAME_DEFAULT_FPS: i32 = 60;
static SET_TARGET_FPS_ADDR: AtomicUsize = AtomicUsize::new(0);
static BYPASS: AtomicBool = AtomicBool::new(false);
pub struct Fps;
impl MhyModule for MhyContext<Fps> {
unsafe fn init(&mut self) -> Result<()> {
let call = util::pattern_scan_il2cpp(self.assembly_name, SET_TARGET_FPS)
.or_else(|| util::pattern_scan_code(self.assembly_name, SET_TARGET_FPS));
let Some(call) = call else {
crate::plog!("Failed to find set_target_fps");
return Ok(());
};
let offset = std::ptr::read_unaligned(call.add(1) as *const i32);
let addr = (call as isize + 5 + offset as isize) as usize;
crate::plog!("set_target_fps: {:x}", addr);
self.interceptor.attach(addr, on_set_target_fps)?;
SET_TARGET_FPS_ADDR.store(addr, Ordering::Relaxed);
Ok(())
}
unsafe fn de_init(&mut self) -> Result<()> {
Ok(())
}
fn get_module_type(&self) -> super::ModuleType {
ModuleType::Fps
}
}
pub fn available() -> bool {
SET_TARGET_FPS_ADDR.load(Ordering::Relaxed) != 0
}
fn target() -> i32 {
match FPS_TARGET.load(Ordering::Relaxed) {
value if value <= 0 => -1,
value => value,
}
}
pub fn apply() {
let addr = SET_TARGET_FPS_ADDR.load(Ordering::Relaxed);
if addr == 0 {
return;
}
let set_target_fps: unsafe extern "win64" fn(i32) = unsafe { std::mem::transmute(addr) };
if FPS_ENABLED.load(Ordering::Relaxed) {
unsafe { set_target_fps(target()) };
} else {
BYPASS.store(true, Ordering::Relaxed);
unsafe { set_target_fps(GAME_DEFAULT_FPS) };
BYPASS.store(false, Ordering::Relaxed);
}
}
unsafe extern "win64" fn on_set_target_fps(reg: *mut Registers, _: usize) {
if FPS_ENABLED.load(Ordering::Relaxed) && !BYPASS.load(Ordering::Relaxed) {
(*reg).rcx = target() as u32 as u64;
}
}
+3
View File
@@ -5,12 +5,14 @@ use anyhow::Result;
use crate::interceptor::Interceptor;
mod ccp_blocker;
pub mod fps;
mod http;
mod misc;
mod security;
mod winhttp;
pub use ccp_blocker::CcpBlocker;
pub use fps::Fps;
pub use http::Http;
pub use misc::Misc;
pub use security::Security;
@@ -47,6 +49,7 @@ pub enum ModuleType {
Misc,
CcpBlocker,
WinHttp,
Fps,
}
pub trait MhyModule {
+153
View File
@@ -0,0 +1,153 @@
use std::io::{Read, Write};
use std::net::{SocketAddr, TcpStream};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Mutex;
use std::time::Duration;
use serde_json::Value;
const HOST: &str = "127.0.0.1";
const PORT: u16 = 8088;
const POLL: Duration = Duration::from_millis(250);
const TIMEOUT: Duration = Duration::from_millis(800);
#[derive(Clone, Default)]
pub struct Row {
pub name: String,
pub owner: String,
pub element: u32,
pub dealt: f64,
pub taken: f64,
pub max_hit: f64,
pub hits: u64,
pub dps: f64,
}
#[derive(Clone, Default)]
pub struct Hit {
pub ago: f64,
pub name: String,
pub target: String,
pub damage: f64,
pub element: u32,
}
#[derive(Clone, Default)]
pub struct Snapshot {
pub connected: bool,
pub online: bool,
pub active: bool,
pub duration: f64,
pub total: f64,
pub total_taken: f64,
pub dps: f64,
pub rows: Vec<Row>,
pub hits: Vec<Hit>,
}
static SNAPSHOT: Mutex<Option<Snapshot>> = Mutex::new(None);
static RESET: AtomicBool = AtomicBool::new(false);
pub fn snapshot() -> Snapshot {
SNAPSHOT
.lock()
.ok()
.and_then(|s| s.clone())
.unwrap_or_default()
}
pub fn request_reset() {
RESET.store(true, Ordering::Relaxed);
}
fn request(method: &str, path: &str) -> Option<String> {
let addr: SocketAddr = format!("{HOST}:{PORT}").parse().ok()?;
let mut stream = TcpStream::connect_timeout(&addr, TIMEOUT).ok()?;
stream.set_read_timeout(Some(TIMEOUT)).ok()?;
stream.set_write_timeout(Some(TIMEOUT)).ok()?;
let request = format!(
"{method} {path} HTTP/1.1\r\nHost: {HOST}:{PORT}\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"
);
stream.write_all(request.as_bytes()).ok()?;
let mut raw = Vec::new();
let _ = stream.read_to_end(&mut raw);
let text = String::from_utf8_lossy(&raw);
let body = text.split_once("\r\n\r\n")?.1;
let start = body.find('{')?;
let end = body.rfind('}')?;
(end >= start).then(|| body[start..=end].to_string())
}
fn str_of(v: &Value, key: &str) -> String {
v[key].as_str().unwrap_or_default().to_string()
}
fn parse(body: &str) -> Option<Snapshot> {
let v: Value = serde_json::from_str(body).ok()?;
let rows = v["rows"]
.as_array()
.map(|rows| {
rows.iter()
.map(|r| Row {
name: str_of(r, "name"),
owner: str_of(r, "owner"),
element: r["element"].as_u64().unwrap_or(0) as u32,
dealt: r["dealt"].as_f64().unwrap_or(0.0),
taken: r["taken"].as_f64().unwrap_or(0.0),
max_hit: r["maxHit"].as_f64().unwrap_or(0.0),
hits: r["hits"].as_u64().unwrap_or(0),
dps: r["dps"].as_f64().unwrap_or(0.0),
})
.collect()
})
.unwrap_or_default();
let hits = v["hits"]
.as_array()
.map(|hits| {
hits.iter()
.map(|h| Hit {
ago: h["ago"].as_f64().unwrap_or(0.0),
name: str_of(h, "name"),
target: str_of(h, "target"),
damage: h["damage"].as_f64().unwrap_or(0.0),
element: h["element"].as_u64().unwrap_or(0) as u32,
})
.collect()
})
.unwrap_or_default();
Some(Snapshot {
connected: true,
online: v["online"].as_bool().unwrap_or(false),
active: v["active"].as_bool().unwrap_or(false),
duration: v["duration"].as_f64().unwrap_or(0.0),
total: v["total"].as_f64().unwrap_or(0.0),
total_taken: v["totalTaken"].as_f64().unwrap_or(0.0),
dps: v["dps"].as_f64().unwrap_or(0.0),
rows,
hits,
})
}
pub fn start() {
std::thread::spawn(|| loop {
if RESET.swap(false, Ordering::Relaxed) {
let _ = request("POST", "/lunagc/damagelog/reset");
}
let next = request("GET", "/lunagc/damagelog")
.and_then(|body| parse(&body))
.unwrap_or_default();
let connected = next.connected;
if let Ok(mut slot) = SNAPSHOT.lock() {
*slot = Some(next);
}
std::thread::sleep(if connected { POLL } else { POLL * 8 });
});
}
+458
View File
@@ -0,0 +1,458 @@
mod client;
use hudhook::hooks::dx11::ImguiDx11Hooks;
use hudhook::imgui::{
Condition, Context, FontConfig, FontId, FontSource, StyleColor, StyleVar, Ui, WindowFlags,
};
use hudhook::windows::Win32::Foundation::HINSTANCE;
use hudhook::{Hudhook, ImguiRenderLoop, RenderContext};
use windows::Win32::UI::Input::KeyboardAndMouse::GetAsyncKeyState;
use std::sync::atomic::Ordering;
use crate::config::{self, FPS_ENABLED, FPS_TARGET, SHOW_PANEL};
use crate::modules::fps;
use client::{Row, Snapshot};
const VK_CONTROL: i32 = 0x11;
const VK_K: i32 = 0x4B;
const VK_L: i32 = 0x4C;
const FPS_PRESETS: [i32; 5] = [60, 120, 144, 165, 240];
const FONT_REGULAR: &str = "C:\\Windows\\Fonts\\segoeui.ttf";
const FONT_BOLD: &str = "C:\\Windows\\Fonts\\segoeuib.ttf";
const FONT_SIZE: f32 = 18.0;
const TEXT: [f32; 4] = [0.93, 0.90, 0.85, 1.0];
const MUTED: [f32; 4] = [0.66, 0.67, 0.72, 1.0];
const GOLD: [f32; 4] = [1.0, 0.84, 0.48, 1.0];
const TRACK: [f32; 4] = [1.0, 1.0, 1.0, 0.08];
const ROW_BG: [f32; 4] = [1.0, 1.0, 1.0, 0.04];
#[derive(Copy, Clone, PartialEq)]
enum Tab {
Dealt,
Taken,
Log,
}
pub struct Overlay {
menu: bool,
menu_held: bool,
panel_held: bool,
fps_input: i32,
tab: Tab,
bold: bool,
}
fn element_color(element: u32) -> [f32; 4] {
match element {
1 => [1.0, 0.45, 0.30, 1.0],
2 => [0.30, 0.68, 1.0, 1.0],
3 => [0.55, 0.85, 0.25, 1.0],
4 => [0.76, 0.52, 1.0, 1.0],
5 | 6 => [0.62, 0.90, 0.98, 1.0],
7 => [0.42, 0.92, 0.78, 1.0],
8 => [1.0, 0.80, 0.32, 1.0],
_ => [0.85, 0.85, 0.88, 1.0],
}
}
fn group(value: f64) -> String {
let digits = (value.max(0.0).round() as u64).to_string();
let mut out = String::with_capacity(digits.len() + digits.len() / 3);
for (i, c) in digits.chars().enumerate() {
if i > 0 && (digits.len() - i) % 3 == 0 {
out.push(',');
}
out.push(c);
}
out
}
fn clock(seconds: f64) -> String {
let s = seconds.max(0.0) as u64;
format!("{:02}:{:02}", s / 60, s % 60)
}
fn load_font(ctx: &mut Context, path: &str) -> Option<FontId> {
let data: &'static [u8] = Box::leak(std::fs::read(path).ok()?.into_boxed_slice());
Some(ctx.fonts().add_font(&[FontSource::TtfData {
data,
size_pixels: FONT_SIZE,
config: Some(FontConfig {
oversample_h: 2,
..FontConfig::default()
}),
}]))
}
impl Overlay {
fn new() -> Self {
Self {
menu: false,
menu_held: false,
panel_held: false,
fps_input: FPS_TARGET.load(Ordering::Relaxed),
tab: Tab::Dealt,
bold: false,
}
}
fn poll_keys(&mut self) {
let ctrl = unsafe { GetAsyncKeyState(VK_CONTROL) < 0 };
let menu = ctrl && unsafe { GetAsyncKeyState(VK_L) < 0 };
let panel = ctrl && unsafe { GetAsyncKeyState(VK_K) < 0 };
if menu && !self.menu_held {
self.menu = !self.menu;
}
if panel && !self.panel_held {
SHOW_PANEL.fetch_xor(true, Ordering::Relaxed);
config::save();
}
self.menu_held = menu;
self.panel_held = panel;
}
fn set_fps(&mut self, value: i32) {
let value = value.clamp(0, 1000);
self.fps_input = value;
FPS_TARGET.store(value, Ordering::Relaxed);
config::save();
fps::apply();
}
fn settings(&mut self, ui: &Ui) {
self.bold_text(ui, GOLD, "Damage statistics");
let mut show = SHOW_PANEL.load(Ordering::Relaxed);
if ui.checkbox("Show damage panel", &mut show) {
SHOW_PANEL.store(show, Ordering::Relaxed);
config::save();
}
ui.text_colored(MUTED, "Ctrl+K toggles the panel anywhere.");
ui.spacing();
ui.separator();
ui.spacing();
self.bold_text(ui, GOLD, "FPS unlocker");
if !fps::available() {
ui.text_colored(MUTED, "Not available on this game version.");
return;
}
let mut enabled = FPS_ENABLED.load(Ordering::Relaxed);
if ui.checkbox("Unlock frame rate", &mut enabled) {
FPS_ENABLED.store(enabled, Ordering::Relaxed);
config::save();
fps::apply();
}
ui.set_next_item_width(140.0);
let submitted = ui
.input_int("Target FPS", &mut self.fps_input)
.enter_returns_true(true)
.build();
if submitted {
self.set_fps(self.fps_input);
}
for preset in FPS_PRESETS {
if ui.button(preset.to_string()) {
self.set_fps(preset);
}
ui.same_line();
}
if ui.button("Unlimited") {
self.set_fps(0);
}
let current = match FPS_TARGET.load(Ordering::Relaxed) {
0 => "unlimited".to_string(),
value => value.to_string(),
};
ui.text_colored(
MUTED,
format!("Current target: {current}. Press Enter to apply a typed value."),
);
}
fn panel(&mut self, ui: &Ui) {
let snap = client::snapshot();
let display = ui.io().display_size;
let _min = ui.push_style_var(StyleVar::WindowMinSize([300.0, 90.0]));
ui.window("##lunagc-damage")
.position([display[0] - 400.0, 150.0], Condition::FirstUseEver)
.size([370.0, 0.0], Condition::FirstUseEver)
.flags(
WindowFlags::NO_TITLE_BAR
| WindowFlags::NO_SCROLLBAR
| WindowFlags::NO_COLLAPSE
| WindowFlags::ALWAYS_AUTO_RESIZE
| WindowFlags::NO_FOCUS_ON_APPEARING
| WindowFlags::NO_NAV,
)
.build(|| {
ui.dummy([342.0, 0.0]);
self.header(ui, &snap);
ui.separator();
match self.tab {
Tab::Dealt => self.rows(ui, &snap, false),
Tab::Taken => self.rows(ui, &snap, true),
Tab::Log => self.log(ui, &snap),
}
self.footer(ui, &snap);
});
}
fn bold_font(&self, ui: &Ui) -> Option<FontId> {
self.bold.then(|| ui.fonts().fonts().get(1).copied()).flatten()
}
fn bold_text(&self, ui: &Ui, color: [f32; 4], text: &str) {
let _font = self.bold_font(ui).map(|id| ui.push_font(id));
ui.text_colored(color, text);
}
fn tab_button(&mut self, ui: &Ui, label: &str, tab: Tab) {
let selected = self.tab == tab;
let _text = ui.push_style_color(StyleColor::Text, if selected { GOLD } else { MUTED });
let _bg = ui.push_style_color(
StyleColor::Button,
if selected { [1.0, 0.84, 0.48, 0.16] } else { [1.0, 1.0, 1.0, 0.05] },
);
if ui.button(label) {
self.tab = tab;
}
}
fn header(&mut self, ui: &Ui, snap: &Snapshot) {
self.bold_text(ui, GOLD, "Damage Statistics");
let status = if !snap.connected {
"server offline"
} else if !snap.online {
"not in game"
} else if snap.active {
"in combat"
} else {
"idle"
};
let width = ui.calc_text_size(status)[0];
ui.same_line_with_pos(ui.window_size()[0] - width - 14.0);
ui.text_colored(if snap.active { GOLD } else { MUTED }, status);
self.tab_button(ui, "Dealt", Tab::Dealt);
ui.same_line();
self.tab_button(ui, "Taken", Tab::Taken);
ui.same_line();
self.tab_button(ui, "Log", Tab::Log);
let width = ui.calc_text_size("Reset")[0] + 20.0;
ui.same_line_with_pos(ui.window_size()[0] - width - 14.0);
let _text = ui.push_style_color(StyleColor::Text, MUTED);
if ui.button_with_size("Reset", [width, 0.0]) {
client::request_reset();
}
}
fn rows(&self, ui: &Ui, snap: &Snapshot, taken: bool) {
let value = |row: &Row| if taken { row.taken } else { row.dealt };
let mut rows: Vec<&Row> = snap.rows.iter().filter(|r| value(r) > 0.0).collect();
rows.sort_by(|a, b| value(b).total_cmp(&value(a)));
if rows.is_empty() {
ui.text_colored(MUTED, if taken { "No damage taken yet." } else { "No damage dealt yet." });
return;
}
let total: f64 = rows.iter().map(|r| value(r)).sum();
let top = value(rows[0]);
let multiplayer = rows.iter().any(|r| r.owner != rows[0].owner);
let line = ui.text_line_height();
let draw = ui.get_window_draw_list();
for (rank, row) in rows.iter().enumerate() {
let color = element_color(row.element);
let amount = value(row);
let origin = ui.cursor_screen_pos();
let width = ui.content_region_avail()[0];
let detail = !taken;
let height = line * if detail { 2.0 } else { 1.0 } + 16.0;
draw.add_rect(origin, [origin[0] + width, origin[1] + height], ROW_BG)
.filled(true)
.rounding(6.0)
.build();
draw.add_rect(origin, [origin[0] + 4.0, origin[1] + height], color)
.filled(true)
.rounding(2.0)
.build();
let left = origin[0] + 12.0;
let right = origin[0] + width - 8.0;
let y = origin[1] + 4.0;
let name = if multiplayer {
format!("{} {} ({})", rank + 1, row.name, row.owner)
} else {
format!("{} {}", rank + 1, row.name)
};
{
let _font = self.bold_font(ui).map(|id| ui.push_font(id));
draw.add_text([left, y], TEXT, &name);
}
let share = if total > 0.0 { amount / total * 100.0 } else { 0.0 };
let share_text = format!("{share:.1}%");
let share_width = ui.calc_text_size(&share_text)[0];
draw.add_text([right - share_width, y], MUTED, &share_text);
let amount_text = group(amount);
{
let _font = self.bold_font(ui).map(|id| ui.push_font(id));
let amount_width = ui.calc_text_size(&amount_text)[0];
draw.add_text([right - 58.0 - amount_width, y], color, &amount_text);
}
let mut bar_y = y + line + 3.0;
if detail {
let stats = format!(
"{}/s {} hits max {}",
group(row.dps),
row.hits,
group(row.max_hit)
);
draw.add_text([left, y + line], MUTED, &stats);
bar_y += line;
}
let fill = if top > 0.0 { (amount / top) as f32 } else { 0.0 };
draw.add_rect([left, bar_y], [right, bar_y + 4.0], TRACK)
.filled(true)
.rounding(2.0)
.build();
draw.add_rect(
[left, bar_y],
[left + (right - left) * fill.clamp(0.02, 1.0), bar_y + 4.0],
color,
)
.filled(true)
.rounding(2.0)
.build();
ui.dummy([width, height]);
}
}
fn log(&self, ui: &Ui, snap: &Snapshot) {
if snap.hits.is_empty() {
ui.text_colored(MUTED, "No hits yet.");
return;
}
let right = ui.window_size()[0] - 14.0;
for hit in snap.hits.iter().rev().take(14) {
ui.text_colored(MUTED, format!("{:>4.0}s", hit.ago));
ui.same_line();
ui.text_colored(TEXT, &hit.name);
ui.same_line();
ui.text_colored(MUTED, format!("> {}", hit.target));
let amount = group(hit.damage);
let width = ui.calc_text_size(&amount)[0];
ui.same_line_with_pos(right - width);
ui.text_colored(element_color(hit.element), amount);
}
}
fn footer(&self, ui: &Ui, snap: &Snapshot) {
ui.separator();
ui.text_colored(MUTED, "Total");
ui.same_line();
let total = if self.tab == Tab::Taken { snap.total_taken } else { snap.total };
self.bold_text(ui, TEXT, &group(total));
let tail = format!("{}/s {}", group(snap.dps), clock(snap.duration));
let width = ui.calc_text_size(&tail)[0];
ui.same_line_with_pos(ui.window_size()[0] - width - 14.0);
ui.text_colored(MUTED, tail);
}
}
impl ImguiRenderLoop for Overlay {
fn initialize<'a>(&'a mut self, ctx: &mut Context, _: &'a mut dyn RenderContext) {
ctx.set_ini_filename(None);
if load_font(ctx, FONT_REGULAR).is_some() {
self.bold = load_font(ctx, FONT_BOLD).is_some();
}
let style = ctx.style_mut();
style.window_rounding = 10.0;
style.window_border_size = 1.0;
style.window_padding = [14.0, 12.0];
style.frame_rounding = 6.0;
style.frame_padding = [10.0, 3.0];
style.item_spacing = [6.0, 6.0];
style.colors[StyleColor::WindowBg as usize] = [0.07, 0.08, 0.11, 0.82];
style.colors[StyleColor::Border as usize] = [1.0, 0.84, 0.48, 0.25];
style.colors[StyleColor::Separator as usize] = [1.0, 1.0, 1.0, 0.10];
style.colors[StyleColor::Text as usize] = TEXT;
style.colors[StyleColor::Button as usize] = [1.0, 1.0, 1.0, 0.05];
style.colors[StyleColor::ButtonHovered as usize] = [1.0, 0.84, 0.48, 0.22];
style.colors[StyleColor::ButtonActive as usize] = [1.0, 0.84, 0.48, 0.32];
style.colors[StyleColor::ResizeGrip as usize] = [0.0, 0.0, 0.0, 0.0];
style.colors[StyleColor::TitleBg as usize] = [0.07, 0.08, 0.11, 0.95];
style.colors[StyleColor::TitleBgActive as usize] = [0.12, 0.13, 0.17, 0.95];
style.colors[StyleColor::FrameBg as usize] = [1.0, 1.0, 1.0, 0.07];
style.colors[StyleColor::FrameBgHovered as usize] = [1.0, 0.84, 0.48, 0.18];
style.colors[StyleColor::FrameBgActive as usize] = [1.0, 0.84, 0.48, 0.28];
style.colors[StyleColor::CheckMark as usize] = GOLD;
}
fn render(&mut self, ui: &mut Ui) {
self.poll_keys();
if SHOW_PANEL.load(Ordering::Relaxed) {
self.panel(ui);
}
if self.menu {
let mut open = true;
ui.window("LunaGC settings")
.position([80.0, 150.0], Condition::FirstUseEver)
.flags(
WindowFlags::ALWAYS_AUTO_RESIZE
| WindowFlags::NO_COLLAPSE
| WindowFlags::NO_SAVED_SETTINGS,
)
.opened(&mut open)
.build(|| self.settings(ui));
self.menu = open;
}
}
}
pub fn start(module: usize) {
client::start();
std::thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_secs(5));
let result = Hudhook::builder()
.with::<ImguiDx11Hooks>(Overlay::new())
.with_hmodule(HINSTANCE(module as _))
.build()
.apply();
match result {
Ok(()) => crate::plog!("Overlay ready: Ctrl+L opens settings, Ctrl+K toggles the damage panel"),
Err(e) => crate::plog!("Damage overlay failed to hook: {e:?}"),
}
});
}