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..9b0a61f
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,424 @@
+# 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",
+ "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.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
+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.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
+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",
+]
+
+[[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 = "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 = "unicode-ident"
+version = "1.0.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
+
+[[package]]
+name = "windows"
+version = "0.58.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6"
+dependencies = [
+ "windows-core",
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.58.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-result",
+ "windows-strings",
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.58.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.58.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
+dependencies = [
+ "windows-result",
+ "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.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",
+ "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_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.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[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.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[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.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[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_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[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.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[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.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[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.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "winnow"
+version = "0.6.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "yanagi"
+version = "0.0.1"
+dependencies = [
+ "ilhook",
+ "num_enum",
+ "thiserror",
+ "windows",
+]
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..9afd5ce
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,12 @@
+[workspace]
+members = ["launcher", "yanagi"]
+resolver = "2"
+
+[workspace.package]
+version = "0.0.1"
+
+[workspace.dependencies]
+windows = "0.58.0"
+ilhook = "2.1.1"
+thiserror = "1.0.64"
+num_enum = "0.7.3"
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..044183d
--- /dev/null
+++ b/launcher/src/main.rs
@@ -0,0 +1,102 @@
+use std::ffi::CString;
+
+use std::ptr::null_mut;
+use windows::core::{s, PCSTR, PSTR};
+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 = "ext.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,
+ PSTR(null_mut()),
+ 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/yanagi/Cargo.toml b/yanagi/Cargo.toml
new file mode 100644
index 0000000..32e74ae
--- /dev/null
+++ b/yanagi/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "yanagi"
+edition = "2021"
+version.workspace = true
+
+[lib]
+name = "yanagi"
+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/yanagi/sdk_public_key.xml b/yanagi/sdk_public_key.xml
new file mode 100644
index 0000000..49b7b3d
--- /dev/null
+++ b/yanagi/sdk_public_key.xml
@@ -0,0 +1 @@
+AQABhEegnKISgDas5VTuRBUlixB+bvmPvXKa3kVO22UEZjPGMUFLmIl3DhH+dsZo7qJn/GfJCUkP1FA0MJ5Bj8PX8IatLJKIJ9dMCNdnAlkXTlMg86QQAhHZN83vP4swj5ILcrGNKl3YAZ49fvzo7nheuTt0/40f0HkHdNa1dUHECBs=
diff --git a/yanagi/server_public_key.xml b/yanagi/server_public_key.xml
new file mode 100644
index 0000000..8ccbcf1
--- /dev/null
+++ b/yanagi/server_public_key.xml
@@ -0,0 +1,8 @@
+
+
+d8zyiJhwWK1Gocou1iynAKbH9f27uAtqFWec+Rbr6G9JFzyAsqH2G7iKqPV60lItLgJ/jvcVyzPeE9eQ1h6Fjw==
+
+
+AQAB
+
+
diff --git a/yanagi/src/interceptor.rs b/yanagi/src/interceptor.rs
new file mode 100644
index 0000000..ee16237
--- /dev/null
+++ b/yanagi/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/yanagi/src/lib.rs b/yanagi/src/lib.rs
new file mode 100644
index 0000000..38655ed
--- /dev/null
+++ b/yanagi/src/lib.rs
@@ -0,0 +1,56 @@
+#![feature(str_from_utf16_endian)]
+
+use std::{thread, time::Duration};
+
+use interceptor::Interceptor;
+use modules::{
+ 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.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/yanagi/src/modules/crypto.rs b/yanagi/src/modules/crypto.rs
new file mode 100644
index 0000000..c094910
--- /dev/null
+++ b/yanagi/src/modules/crypto.rs
@@ -0,0 +1,101 @@
+use std::ffi::CString;
+
+use ilhook::x64::Registers;
+
+use crate::{
+ interceptor::Interceptor,
+ util::{import, read_csharp_string, GAME_ASSEMBLY_BASE},
+};
+
+import!(rsa_create() -> usize = 0x163AEC10);
+import!(rsa_from_xml_string(instance: usize, xml_string: usize) -> usize = 0x163AEE50);
+import!(il2cpp_string_new(cstr: *const u8) -> usize = 0x72540);
+
+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(0x44949B0) as *const usize)) + 204776) 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(0x475FB40) as *mut usize) = il2cpp_string_new(
+ CString::new(SDK_PUBLIC_KEY)
+ .unwrap()
+ .to_bytes_with_nul()
+ .as_ptr(),
+ ) as usize;
+}
+
+pub unsafe fn monitor_network_state(interceptor: &mut Interceptor) {
+ interceptor
+ .attach(
+ GAME_ASSEMBLY_BASE.wrapping_add(0xA27D650),
+ on_network_state_change,
+ )
+ .unwrap();
+
+ interceptor
+ .attach(
+ GAME_ASSEMBLY_BASE.wrapping_add(0x97DA670),
+ 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/yanagi/src/modules/mod.rs b/yanagi/src/modules/mod.rs
new file mode 100644
index 0000000..ac80bc4
--- /dev/null
+++ b/yanagi/src/modules/mod.rs
@@ -0,0 +1,56 @@
+use std::marker::PhantomData;
+
+use crate::{interceptor::Interceptor, util};
+
+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/yanagi/src/modules/network.rs b/yanagi/src/modules/network.rs
new file mode 100644
index 0000000..7678740
--- /dev/null
+++ b/yanagi/src/modules/network.rs
@@ -0,0 +1,79 @@
+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 = 0x605A190;
+
+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(0x17627E10), on_set_url)?;
+
+ Ok(())
+ }
+}
+
+unsafe extern "win64" fn on_set_url(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 = 0x72540);
+
+impl Network {
+ const SDK_URL: &str = "http://127.0.0.1:10001";
+ const DISPATCH_URL: &str = "http://127.0.0.1:10002";
+ 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/yanagi/src/util.rs b/yanagi/src/util.rs
new file mode 100644
index 0000000..e926143
--- /dev/null
+++ b/yanagi/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();
+}