This commit is contained in:
xeon 2024-11-25 02:45:23 +03:00
parent 20d7158784
commit 23433635ae
15 changed files with 994 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
target/

424
Cargo.lock generated Normal file
View file

@ -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",
]

12
Cargo.toml Normal file
View file

@ -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"

14
launcher/Cargo.toml Normal file
View file

@ -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",
] }

102
launcher/src/main.rs Normal file
View file

@ -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();
}
}

2
rust-toolchain.toml Normal file
View file

@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"

21
yanagi/Cargo.toml Normal file
View file

@ -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",
] }

View file

@ -0,0 +1 @@
<RSAKeyValue><Exponent>AQAB</Exponent><Modulus>hEegnKISgDas5VTuRBUlixB+bvmPvXKa3kVO22UEZjPGMUFLmIl3DhH+dsZo7qJn/GfJCUkP1FA0MJ5Bj8PX8IatLJKIJ9dMCNdnAlkXTlMg86QQAhHZN83vP4swj5ILcrGNKl3YAZ49fvzo7nheuTt0/40f0HkHdNa1dUHECBs=</Modulus></RSAKeyValue>

View file

@ -0,0 +1,8 @@
<RSAKeyValue>
<Modulus>
d8zyiJhwWK1Gocou1iynAKbH9f27uAtqFWec+Rbr6G9JFzyAsqH2G7iKqPV60lItLgJ/jvcVyzPeE9eQ1h6Fjw==
</Modulus>
<Exponent>
AQAB
</Exponent>
</RSAKeyValue>

42
yanagi/src/interceptor.rs Normal file
View file

@ -0,0 +1,42 @@
use ilhook::x64::{
CallbackOption, HookFlags, HookPoint, HookType, Hooker, JmpBackRoutine, RetnRoutine,
};
#[derive(Default)]
pub struct Interceptor {
hooks: Vec<HookPoint>,
}
type Result<T> = std::result::Result<T, ilhook::HookError>;
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(())
}
}

56
yanagi/src/lib.rs Normal file
View file

@ -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::<Network>();
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
}

View file

@ -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,
}

56
yanagi/src/modules/mod.rs Normal file
View file

@ -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<T> {
base: usize,
interceptor: Interceptor,
_module_type: PhantomData<T>,
}
impl<T> NapModuleContext<T> {
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<Box<dyn NapModule>>,
}
impl NapModuleManager {
pub fn add<T: 'static>(&mut self)
where
NapModuleContext<T>: NapModule,
{
self.modules.push(Box::new(NapModuleContext::<T>::new(
*util::GAME_ASSEMBLY_BASE,
)));
}
pub unsafe fn init(&mut self) -> Result<(), ModuleInitError> {
for module in self.modules.iter_mut() {
module.init()?;
}
Ok(())
}
}

View file

@ -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<Network> {
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;
}
}

75
yanagi/src/util.rs Normal file
View file

@ -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<usize> = ::std::sync::LazyLock::new(|| *crate::util::GAME_ASSEMBLY_BASE + $rva);
type FuncType = unsafe extern "fastcall" fn($($arg_type,)*) -> $ret_type;
::std::mem::transmute::<usize, FuncType>(*PROC)($($arg_name,)*)
}
};
}
pub(crate) use import;
pub static GAME_ASSEMBLY_BASE: LazyLock<usize> =
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();
}