diff --git a/.cargo/config.toml b/.cargo/config.toml
new file mode 100644
index 0000000..ba54493
--- /dev/null
+++ b/.cargo/config.toml
@@ -0,0 +1,2 @@
+[build]
+target = "x86_64-pc-windows-gnu"
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2f7896d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+target/
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..2609267
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,443 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "bitflags"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
+
+[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
+[[package]]
+name = "hashbrown"
+version = "0.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3"
+
+[[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.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7900295ca4c0c37e0325ddbf4b2b78cecabdb36d75d02712c31fb7d777e658f6"
+dependencies = [
+ "bitflags",
+ "iced-x86",
+ "lazy_static",
+ "libc",
+ "regex",
+ "thiserror 1.0.64",
+ "windows-sys",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
+name = "launcher"
+version = "0.0.1"
+dependencies = [
+ "windows",
+]
+
+[[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.159"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
+
+[[package]]
+name = "memchr"
+version = "2.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+
+[[package]]
+name = "num_enum"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179"
+dependencies = [
+ "num_enum_derive",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b"
+dependencies = [
+ "toml_edit",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "regex"
+version = "1.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
+
+[[package]]
+name = "syn"
+version = "2.0.96"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.64"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
+dependencies = [
+ "thiserror-impl 1.0.64",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc"
+dependencies = [
+ "thiserror-impl 2.0.11",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.64"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
+
+[[package]]
+name = "toml_edit"
+version = "0.22.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
+dependencies = [
+ "indexmap",
+ "toml_datetime",
+ "winnow",
+]
+
+[[package]]
+name = "trigger"
+version = "0.0.1"
+dependencies = [
+ "ilhook",
+ "num_enum",
+ "thiserror 2.0.11",
+ "windows",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
+
+[[package]]
+name = "windows"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f919aee0a93304be7f62e8e5027811bbba96bcb1de84d6618be56e43f8a32a1"
+dependencies = [
+ "windows-core",
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "810ce18ed2112484b0d4e15d022e5f598113e220c53e373fb31e67e21670c1ce"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-result",
+ "windows-strings",
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb26fd936d991781ea39e87c3a27285081e3c0da5ca0fcbc02d368cc6f52ff01"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d08106ce80268c4067c0571ca55a9b4e9516518eaa1a1fe9b37ca403ae1d1a34"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b888f919960b42ea4e11c2f408fadb55f78a9f236d5eef084103c8ce52893491"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
+dependencies = [
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b"
+dependencies = [
+ "windows_aarch64_gnullvm 0.53.0",
+ "windows_aarch64_msvc 0.53.0",
+ "windows_i686_gnu 0.53.0",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc 0.53.0",
+ "windows_x86_64_gnu 0.53.0",
+ "windows_x86_64_gnullvm 0.53.0",
+ "windows_x86_64_msvc 0.53.0",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
+
+[[package]]
+name = "winnow"
+version = "0.6.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b"
+dependencies = [
+ "memchr",
+]
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..846da0e
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,12 @@
+[workspace]
+members = ["launcher", "trigger"]
+resolver = "2"
+
+[workspace.package]
+version = "0.0.1"
+
+[workspace.dependencies]
+windows = "0.59.0"
+ilhook = "2.1.1"
+thiserror = "2.0.11"
+num_enum = "0.7.3"
diff --git a/README.md b/README.md
index 557ecea..e3bef23 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,2 @@
# trigger-patch
-Patch the Patch to unleash the power of Patches
\ No newline at end of file
diff --git a/launcher/Cargo.toml b/launcher/Cargo.toml
new file mode 100644
index 0000000..6dcfaf0
--- /dev/null
+++ b/launcher/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "launcher"
+edition = "2021"
+version.workspace = true
+
+[dependencies]
+windows = { workspace = true, features = [
+ "Win32_Foundation",
+ "Win32_System_Diagnostics_Debug",
+ "Win32_System_LibraryLoader",
+ "Win32_System_Memory",
+ "Win32_System_Threading",
+ "Win32_Security",
+] }
diff --git a/launcher/src/main.rs b/launcher/src/main.rs
new file mode 100644
index 0000000..f478e18
--- /dev/null
+++ b/launcher/src/main.rs
@@ -0,0 +1,101 @@
+use std::ffi::CString;
+
+use windows::core::{s, PCSTR};
+use windows::Win32::Foundation::{CloseHandle, GetLastError, HANDLE};
+use windows::Win32::System::Diagnostics::Debug::WriteProcessMemory;
+use windows::Win32::System::LibraryLoader::{GetModuleHandleA, GetProcAddress};
+use windows::Win32::System::Memory::{
+ VirtualAllocEx, VirtualFreeEx, MEM_COMMIT, MEM_RELEASE, MEM_RESERVE, PAGE_READWRITE,
+};
+use windows::Win32::System::Threading::{
+ CreateProcessA, CreateRemoteThread, ResumeThread, WaitForSingleObject, CREATE_SUSPENDED,
+ PROCESS_INFORMATION, STARTUPINFOA,
+};
+
+const GAME_EXECUTABLE: PCSTR = s!("ZenlessZoneZeroBeta.exe");
+const INJECT_DLL: &str = "trigger.dll";
+
+fn inject_standard(h_target: HANDLE, dll_path: &str) -> bool {
+ unsafe {
+ let loadlib = GetProcAddress(
+ GetModuleHandleA(s!("kernel32.dll")).unwrap(),
+ s!("LoadLibraryA"),
+ )
+ .unwrap();
+
+ let dll_path_cstr = CString::new(dll_path).unwrap();
+ let dll_path_addr = VirtualAllocEx(
+ h_target,
+ None,
+ dll_path_cstr.to_bytes_with_nul().len(),
+ MEM_COMMIT | MEM_RESERVE,
+ PAGE_READWRITE,
+ );
+
+ if dll_path_addr.is_null() {
+ println!("VirtualAllocEx failed. Last error: {:?}", GetLastError());
+ return false;
+ }
+
+ WriteProcessMemory(
+ h_target,
+ dll_path_addr,
+ dll_path_cstr.as_ptr() as _,
+ dll_path_cstr.to_bytes_with_nul().len(),
+ None,
+ )
+ .unwrap();
+
+ let h_thread = CreateRemoteThread(
+ h_target,
+ None,
+ 0,
+ Some(std::mem::transmute(loadlib)),
+ Some(dll_path_addr),
+ 0,
+ None,
+ )
+ .unwrap();
+
+ WaitForSingleObject(h_thread, 0xFFFFFFFF);
+
+ VirtualFreeEx(h_target, dll_path_addr, 0, MEM_RELEASE).unwrap();
+ CloseHandle(h_thread).unwrap();
+ true
+ }
+}
+
+fn main() {
+ let current_dir = std::env::current_dir().unwrap();
+ let dll_path = current_dir.join(INJECT_DLL);
+ if !dll_path.is_file() {
+ println!("{INJECT_DLL} not found");
+ return;
+ }
+
+ let mut proc_info = PROCESS_INFORMATION::default();
+ let mut startup_info = STARTUPINFOA::default();
+
+ unsafe {
+ CreateProcessA(
+ GAME_EXECUTABLE,
+ None,
+ None,
+ None,
+ false,
+ CREATE_SUSPENDED,
+ None,
+ None,
+ &mut startup_info,
+ &mut proc_info,
+ )
+ .unwrap();
+
+ if inject_standard(proc_info.hProcess, dll_path.to_str().unwrap()) {
+ ResumeThread(proc_info.hThread);
+ }
+
+ CloseHandle(proc_info.hThread).unwrap();
+ CloseHandle(proc_info.hProcess).unwrap();
+ }
+}
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
new file mode 100644
index 0000000..5d56faf
--- /dev/null
+++ b/rust-toolchain.toml
@@ -0,0 +1,2 @@
+[toolchain]
+channel = "nightly"
diff --git a/trigger/Cargo.toml b/trigger/Cargo.toml
new file mode 100644
index 0000000..725dae5
--- /dev/null
+++ b/trigger/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "trigger"
+edition = "2021"
+version.workspace = true
+
+[lib]
+name = "trigger"
+crate-type = ["cdylib"]
+
+[dependencies]
+ilhook.workspace = true
+thiserror.workspace = true
+num_enum.workspace = true
+windows = { workspace = true, features = [
+ "Win32_Foundation",
+ "Win32_System_SystemServices",
+ "Win32_System_LibraryLoader",
+ "Win32_System_Console",
+ "Win32_System_Threading",
+ "Win32_System_Memory",
+] }
diff --git a/trigger/sdk_public_key.xml b/trigger/sdk_public_key.xml
new file mode 100644
index 0000000..49b7b3d
--- /dev/null
+++ b/trigger/sdk_public_key.xml
@@ -0,0 +1 @@
+AQABhEegnKISgDas5VTuRBUlixB+bvmPvXKa3kVO22UEZjPGMUFLmIl3DhH+dsZo7qJn/GfJCUkP1FA0MJ5Bj8PX8IatLJKIJ9dMCNdnAlkXTlMg86QQAhHZN83vP4swj5ILcrGNKl3YAZ49fvzo7nheuTt0/40f0HkHdNa1dUHECBs=
diff --git a/trigger/server_public_key.xml b/trigger/server_public_key.xml
new file mode 100644
index 0000000..8ccbcf1
--- /dev/null
+++ b/trigger/server_public_key.xml
@@ -0,0 +1,8 @@
+
+
+d8zyiJhwWK1Gocou1iynAKbH9f27uAtqFWec+Rbr6G9JFzyAsqH2G7iKqPV60lItLgJ/jvcVyzPeE9eQ1h6Fjw==
+
+
+AQAB
+
+
diff --git a/trigger/src/interceptor.rs b/trigger/src/interceptor.rs
new file mode 100644
index 0000000..ee16237
--- /dev/null
+++ b/trigger/src/interceptor.rs
@@ -0,0 +1,42 @@
+use ilhook::x64::{
+ CallbackOption, HookFlags, HookPoint, HookType, Hooker, JmpBackRoutine, RetnRoutine,
+};
+
+#[derive(Default)]
+pub struct Interceptor {
+ hooks: Vec,
+}
+
+type Result = std::result::Result;
+impl Interceptor {
+ pub unsafe fn attach(&mut self, addr: usize, routine: JmpBackRoutine) -> Result<()> {
+ let hooker = Hooker::new(
+ addr,
+ HookType::JmpBack(routine),
+ CallbackOption::None,
+ 0,
+ HookFlags::empty(),
+ );
+
+ let hook_point = hooker.hook()?;
+ self.hooks.push(hook_point);
+
+ Ok(())
+ }
+
+ #[expect(dead_code)]
+ pub unsafe fn replace(&mut self, addr: usize, routine: RetnRoutine) -> Result<()> {
+ let hooker = Hooker::new(
+ addr,
+ HookType::Retn(routine),
+ CallbackOption::None,
+ 0,
+ HookFlags::empty(),
+ );
+
+ let hook_point = hooker.hook()?;
+ self.hooks.push(hook_point);
+
+ Ok(())
+ }
+}
diff --git a/trigger/src/lib.rs b/trigger/src/lib.rs
new file mode 100644
index 0000000..690ff8c
--- /dev/null
+++ b/trigger/src/lib.rs
@@ -0,0 +1,58 @@
+#![feature(str_from_utf16_endian)]
+
+use std::{thread, time::Duration};
+
+use interceptor::Interceptor;
+use modules::{
+ censorship_patch::CensorshipPatch,
+ crypto::{
+ initialize_rsa_public_key, monitor_network_state, replace_sdk_public_key_string_literal,
+ },
+ network::Network,
+ NapModuleManager,
+};
+use windows::{
+ core::s,
+ Win32::{
+ Foundation::HINSTANCE,
+ System::{Console, LibraryLoader::GetModuleHandleA, SystemServices::DLL_PROCESS_ATTACH},
+ },
+};
+
+mod interceptor;
+mod modules;
+mod util;
+
+unsafe fn thread_fn() {
+ let _ = Console::AllocConsole();
+
+ while GetModuleHandleA(s!("GameAssembly.dll")).is_err() {
+ thread::sleep(Duration::from_millis(200));
+ }
+
+ thread::sleep(Duration::from_secs(5));
+ util::disable_memory_protection();
+
+ let mut module_manager = NapModuleManager::default();
+ module_manager.add::();
+ module_manager.add::();
+ module_manager.init().expect("failed to initialize modules");
+
+ initialize_rsa_public_key();
+ replace_sdk_public_key_string_literal();
+
+ let mut interceptor = Interceptor::default();
+ monitor_network_state(&mut interceptor);
+
+ thread::sleep(Duration::from_secs(u64::MAX));
+}
+
+#[no_mangle]
+#[allow(non_snake_case)]
+unsafe extern "system" fn DllMain(_: HINSTANCE, call_reason: u32, _: *mut ()) -> bool {
+ if call_reason == DLL_PROCESS_ATTACH {
+ thread::spawn(|| thread_fn());
+ }
+
+ true
+}
diff --git a/trigger/src/modules/censorship_patch.rs b/trigger/src/modules/censorship_patch.rs
new file mode 100644
index 0000000..81a74fb
--- /dev/null
+++ b/trigger/src/modules/censorship_patch.rs
@@ -0,0 +1,25 @@
+use ilhook::x64::Registers;
+
+use super::{ModuleInitError, NapModule, NapModuleContext};
+
+const SET_DITHER_CONFIG: usize = 0x8C9D790;
+
+pub struct CensorshipPatch;
+
+impl NapModule for NapModuleContext {
+ unsafe fn init(&mut self) -> Result<(), ModuleInitError> {
+ self.interceptor.attach(
+ self.base.wrapping_add(SET_DITHER_CONFIG),
+ CensorshipPatch::on_set_dither_config,
+ )?;
+
+ Ok(())
+ }
+}
+
+impl CensorshipPatch {
+ pub unsafe extern "win64" fn on_set_dither_config(reg: *mut Registers, _: usize) {
+ println!("SetDitherConfig()");
+ (*reg).rdx = 0;
+ }
+}
diff --git a/trigger/src/modules/crypto.rs b/trigger/src/modules/crypto.rs
new file mode 100644
index 0000000..2e563d4
--- /dev/null
+++ b/trigger/src/modules/crypto.rs
@@ -0,0 +1,120 @@
+use std::ffi::CString;
+
+use ilhook::x64::Registers;
+
+use crate::{
+ interceptor::Interceptor,
+ util::{import, read_csharp_string, GAME_ASSEMBLY_BASE},
+};
+
+import!(rsa_create() -> usize = 0x18753130);
+import!(rsa_from_xml_string(instance: usize, xml_string: usize) -> usize = 0x18753370);
+import!(il2cpp_string_new(cstr: *const u8) -> usize = 0x85BC0);
+
+pub unsafe fn initialize_rsa_public_key() {
+ const SERVER_PUBLIC_KEY: &str = include_str!("../../server_public_key.xml");
+ let rsa_public_key_backdoor_field =
+ ((*(GAME_ASSEMBLY_BASE.wrapping_add(0x4AEDE00) as *const usize)) + 237672) as *mut usize;
+
+ let rsa = rsa_create();
+ rsa_from_xml_string(
+ rsa,
+ il2cpp_string_new(
+ CString::new(SERVER_PUBLIC_KEY)
+ .unwrap()
+ .to_bytes_with_nul()
+ .as_ptr(),
+ ),
+ );
+
+ *rsa_public_key_backdoor_field = rsa;
+}
+
+pub unsafe fn replace_sdk_public_key_string_literal() {
+ const SDK_PUBLIC_KEY: &str = include_str!("../../sdk_public_key.xml");
+
+ *(GAME_ASSEMBLY_BASE.wrapping_add(0x4DF89C8) as *mut usize) = il2cpp_string_new(
+ CString::new(SDK_PUBLIC_KEY)
+ .unwrap()
+ .to_bytes_with_nul()
+ .as_ptr(),
+ ) as usize;
+
+ *(GAME_ASSEMBLY_BASE.wrapping_add(0x4E17B68) as *mut usize) = il2cpp_string_new(
+ [
+ 27818, 40348, 47410, 27936, 51394, 33172, 51987, 33287, 44524, 39195, 47922, 8238,
+ 53932, 42445, 929, 38470, 27758, 56475, 5938, 26471, 58462, 55701, 37675, 22326, 36428,
+ 56283, 47542, 26144, 58590, 46209, 37755, 1618, 11725, 39897, 4119,
+ ]
+ .into_iter()
+ .enumerate()
+ .flat_map(|(i, v)| {
+ let b = (((i + ((i >> 31) >> 29)) & 0xF8).wrapping_sub(i)) as i16;
+ (((v << ((b + 11) & 0xF)) | (v >> ((-11 - b) & 0xF))) & 0xFFFF_u16)
+ .to_be_bytes()
+ .into_iter()
+ })
+ .chain([0])
+ .collect::>()
+ .as_ptr(),
+ ) as usize;
+}
+
+pub unsafe fn monitor_network_state(interceptor: &mut Interceptor) {
+ interceptor
+ .attach(
+ GAME_ASSEMBLY_BASE.wrapping_add(0x9B23090),
+ on_network_state_change,
+ )
+ .unwrap();
+
+ interceptor
+ .attach(
+ GAME_ASSEMBLY_BASE.wrapping_add(0xB250980),
+ download_data_slave,
+ )
+ .unwrap();
+}
+
+unsafe extern "win64" fn download_data_slave(reg: *mut Registers, _: usize) {
+ let data = read_csharp_string((*reg).rcx as usize);
+ println!("{data}");
+}
+
+unsafe extern "win64" fn on_network_state_change(reg: *mut Registers, _: usize) {
+ let net_state = NetworkState::from((*reg).rcx);
+ println!("network state change: {net_state:?}");
+
+ if net_state == NetworkState::PlayerLoginCsReq {
+ // public key rsa gets reset to null after successful PlayerGetTokenScRsp
+ initialize_rsa_public_key();
+ }
+}
+
+#[repr(u64)]
+#[derive(num_enum::FromPrimitive, Debug, Default, PartialEq)]
+pub enum NetworkState {
+ CloudCmdLine = 21,
+ CloudDispatch = 20,
+ StartBasicsReq = 17,
+ LoadShaderEnd = 9,
+ PlayerLoginCsReq = 15,
+ EndBasicsReq = 18,
+ LoadResourcesEnd = 10,
+ GlobalDispatch = 1,
+ ConnectGameServer = 12,
+ ChooseServer = 2,
+ DoFileVerifyEnd = 7,
+ PlayerLoginScRsp = 16,
+ DispatchResult = 4,
+ PlayerGetTokenScRsp = 14,
+ DownloadResourcesEnd = 6,
+ AccountLogin = 3,
+ LoadAssetEnd = 8,
+ StartEnterGameWorld = 11,
+ #[default]
+ None = 0,
+ EnterWorldScRsp = 19,
+ PlayerGetTokenCsReq = 13,
+ StartDownLoad = 5,
+}
diff --git a/trigger/src/modules/mod.rs b/trigger/src/modules/mod.rs
new file mode 100644
index 0000000..dcd4437
--- /dev/null
+++ b/trigger/src/modules/mod.rs
@@ -0,0 +1,57 @@
+use std::marker::PhantomData;
+
+use crate::{interceptor::Interceptor, util};
+
+pub mod censorship_patch;
+pub mod crypto;
+pub mod network;
+
+#[derive(thiserror::Error, Debug)]
+pub enum ModuleInitError {
+ #[error("{0}")]
+ HookFail(#[from] ilhook::HookError),
+}
+
+pub struct NapModuleContext {
+ base: usize,
+ interceptor: Interceptor,
+ _module_type: PhantomData,
+}
+
+impl NapModuleContext {
+ fn new(base: usize) -> Self {
+ Self {
+ base,
+ interceptor: Interceptor::default(),
+ _module_type: PhantomData,
+ }
+ }
+}
+
+pub trait NapModule {
+ unsafe fn init(&mut self) -> Result<(), ModuleInitError>;
+}
+
+#[derive(Default)]
+pub struct NapModuleManager {
+ modules: Vec>,
+}
+
+impl NapModuleManager {
+ pub fn add(&mut self)
+ where
+ NapModuleContext: NapModule,
+ {
+ self.modules.push(Box::new(NapModuleContext::::new(
+ *util::GAME_ASSEMBLY_BASE,
+ )));
+ }
+
+ pub unsafe fn init(&mut self) -> Result<(), ModuleInitError> {
+ for module in self.modules.iter_mut() {
+ module.init()?;
+ }
+
+ Ok(())
+ }
+}
diff --git a/trigger/src/modules/network.rs b/trigger/src/modules/network.rs
new file mode 100644
index 0000000..73637e5
--- /dev/null
+++ b/trigger/src/modules/network.rs
@@ -0,0 +1,82 @@
+use std::ffi::CString;
+
+use ilhook::x64::Registers;
+
+use crate::util::{self, import, read_csharp_string};
+
+use super::{ModuleInitError, NapModule, NapModuleContext};
+
+const MAKE_INITIAL_URL: usize = 0x6A37670;
+const WEB_REQUEST_CREATE: usize = 0x19910820;
+
+pub struct Network;
+
+impl NapModule for NapModuleContext {
+ unsafe fn init(&mut self) -> Result<(), ModuleInitError> {
+ self.interceptor.attach(
+ self.base.wrapping_add(MAKE_INITIAL_URL),
+ Network::on_make_initial_url,
+ )?;
+
+ self.interceptor.attach(
+ self.base.wrapping_add(WEB_REQUEST_CREATE),
+ on_web_request_create,
+ )?;
+
+ Ok(())
+ }
+}
+
+unsafe extern "win64" fn on_web_request_create(reg: *mut Registers, _: usize) {
+ let s = read_csharp_string((*reg).rcx as usize);
+ if s.contains("StandaloneWindows64/cn/") {
+ let s = s.replace("StandaloneWindows64/cn/", "StandaloneWindows64/oversea/");
+ println!("replaced: {s}");
+ (*reg).rcx =
+ il2cpp_string_new(CString::new(s).unwrap().to_bytes_with_nul().as_ptr()) as u64;
+ }
+}
+
+import!(il2cpp_string_new(cstr: *const u8) -> usize = 0x85BC0);
+
+impl Network {
+ const SDK_URL: &str = "http://127.0.0.1:20100";
+ const DISPATCH_URL: &str = "http://127.0.0.1:10100";
+ const REDIRECT_SDK: bool = true;
+ const REDIRECT_DISPATCH: bool = true;
+
+ unsafe extern "win64" fn on_make_initial_url(reg: *mut Registers, _: usize) {
+ let url = util::read_csharp_string((*reg).rcx as usize);
+
+ let mut new_url = match url.as_str() {
+ s if (s.contains("mihoyo.com") || s.contains("hoyoverse.com"))
+ && Self::REDIRECT_SDK =>
+ {
+ Self::SDK_URL.to_string()
+ }
+ s if (s.contains("globaldp-prod-os01.zenlesszonezero.com")
+ || s.contains("globaldp-prod-cn01.juequling.com"))
+ && Self::REDIRECT_DISPATCH =>
+ {
+ Self::DISPATCH_URL.to_string()
+ }
+ s => {
+ println!("Leaving request as-is: {s}");
+ return;
+ }
+ };
+
+ url.split('/').skip(3).for_each(|s| {
+ new_url.push_str("/");
+ new_url.push_str(s);
+ });
+
+ println!("UnityWebRequest: \"{url}\", replacing with \"{new_url}\"");
+ (*reg).rcx = il2cpp_string_new(
+ CString::new(new_url.as_str())
+ .unwrap()
+ .to_bytes_with_nul()
+ .as_ptr(),
+ ) as u64;
+ }
+}
diff --git a/trigger/src/util.rs b/trigger/src/util.rs
new file mode 100644
index 0000000..e926143
--- /dev/null
+++ b/trigger/src/util.rs
@@ -0,0 +1,75 @@
+use std::{ffi::c_void, sync::LazyLock};
+
+use windows::{
+ core::s,
+ Win32::System::{
+ LibraryLoader::{GetModuleHandleA, GetProcAddress},
+ Memory,
+ },
+};
+
+#[macro_export]
+macro_rules! c {
+ ($cstr:expr) => {
+ unsafe {
+ std::ffi::CStr::from_ptr(concat!($cstr, "\0").as_ptr() as *const std::os::raw::c_char)
+ .to_bytes_with_nul()
+ .as_ptr()
+ }
+ };
+}
+
+macro_rules! import {
+ ($name:ident($($arg_name:ident: $arg_type:ty),*) -> $ret_type:ty = $rva:expr) => {
+ pub unsafe fn $name($($arg_name: $arg_type,)*) -> $ret_type {
+ static PROC: ::std::sync::LazyLock = ::std::sync::LazyLock::new(|| *crate::util::GAME_ASSEMBLY_BASE + $rva);
+
+ type FuncType = unsafe extern "fastcall" fn($($arg_type,)*) -> $ret_type;
+ ::std::mem::transmute::(*PROC)($($arg_name,)*)
+ }
+ };
+}
+
+pub(crate) use import;
+
+pub static GAME_ASSEMBLY_BASE: LazyLock =
+ LazyLock::new(|| unsafe { GetModuleHandleA(s!("GameAssembly.dll")).unwrap().0 as usize });
+
+#[inline]
+pub unsafe fn read_csharp_string(s: usize) -> String {
+ let str_length = *(s.wrapping_add(16) as *const u32);
+ let str_ptr = s.wrapping_add(20) as *const u8;
+
+ String::from_utf16le_lossy(std::slice::from_raw_parts(
+ str_ptr,
+ (str_length * 2) as usize,
+ ))
+}
+
+pub unsafe fn disable_memory_protection() {
+ let ntdll = GetModuleHandleA(s!("ntdll.dll")).unwrap();
+ let proc_addr = GetProcAddress(ntdll, s!("NtProtectVirtualMemory")).unwrap();
+
+ let nt_func = if GetProcAddress(ntdll, s!("wine_get_version")).is_some() {
+ GetProcAddress(ntdll, s!("NtPulseEvent")).unwrap()
+ } else {
+ GetProcAddress(ntdll, s!("NtQuerySection")).unwrap()
+ };
+
+ let mut prot = Memory::PAGE_EXECUTE_READWRITE;
+ Memory::VirtualProtect(proc_addr as *const usize as *mut c_void, 1, prot, &mut prot).unwrap();
+
+ let routine = nt_func as *mut u32;
+ let routine_val = *(routine as *const usize);
+
+ let lower_bits_mask = !(0xFFu64 << 32);
+ let lower_bits = routine_val & lower_bits_mask as usize;
+
+ let offset_val = *((routine as usize + 4) as *const u32);
+ let upper_bits = ((offset_val as usize).wrapping_sub(1) as usize) << 32;
+
+ let result = lower_bits | upper_bits;
+
+ *(proc_addr as *mut usize) = result;
+ Memory::VirtualProtect(proc_addr as *const usize as *mut c_void, 1, prot, &mut prot).unwrap();
+}