Compare commits

5 Commits

Author SHA1 Message Date
amizing25
7ab400507e feat(launcher): warn if the launcher is not run as admin 2025-08-22 06:08:34 +07:00
amizing25
d66398b9b8 fix: fixed client damaged error 2025-08-22 06:04:37 +07:00
amizing25
ee7fa339fb refactor: minor changes
- use VA directly on interceptor instead of RVA
2025-08-22 05:53:53 +07:00
amizing25
cc2f1865fc fix: update set_dither pattern 2025-05-27 10:54:20 +07:00
amizing25
0c87252394 fix censorship patch 2025-05-23 08:23:35 +07:00
12 changed files with 149 additions and 85 deletions

View File

@@ -4,13 +4,13 @@ use windows::{Win32::System::LibraryLoader::GetModuleHandleA, core::s};
use crate::util::scan_il2cpp_section; use crate::util::scan_il2cpp_section;
const IL2CPP_STRING_NEW_LEN: &str = "E8 ? ? ? ? EB ? 31 C0 48 89 06 48 8B 47 ? 48 89 46 ? F2 0F 10 47"; const IL2CPP_STRING_NEW_LEN: &str =
const MAKE_INITIAL_URL: &str = "E8 ? ? ? ? 48 89 D9 48 89 C2 E8 ? ? ? ? 48 89 D9 4C 89 FA E8 ? ? ? ? 49 89 5D"; // TODO "E8 ? ? ? ? EB ? 31 C0 48 89 06 48 8B 47 ? 48 89 46 ? F2 0F 10 47";
const SET_ELEVATION_DITHER: &str = "56 48 83 EC ? 0F 29 74 24 ? 0F 28 F1 48 89 CE 80 3D ? ? ? ? ? 75 ? 80 7E ? ? 74 ? 0F 57 C0 F3 0F 5F C6 F3 0F 10 0D ? ? ? ? F3 0F 5D C8 F3 0F 11 4E ? F3 0F 59 4E ? 48 89 F1 41 B8 ? ? ? ? E8 ? ? ? ? 84 C0 75 ? C7 46 ? ? ? ? ? 0F 28 74 24 ? 48 83 C4 ? 5E C3 B9 ? ? ? ? E8 ? ? ? ? 48 85 C0 74 ? 48 89 C1 48 89 F2 0F 28 D6 0F 28 74 24 ? 48 83 C4 ? 5E E9 ? ? ? ? E8 ? ? ? ? CC 0F 1F 00 56 57 53"; // TODO const MAKE_INITIAL_URL: &str =
const SET_DISTANCE_DITHER: &str = "E8 ? ? ? ? 49 8B 46 ? 48 85 C0 0F 84 ? ? ? ? 48 8B 4D"; // TODO "E8 ? ? ? ? 48 89 D9 48 89 C2 E8 ? ? ? ? 48 89 D9 4C 89 FA E8 ? ? ? ? 49 89 5D"; // TODO
const SET_DITHER_ALPHA: &str = "E8 ? ? ? ? 0F 28 74 24 ? 48 83 C4 ? 5B 5F 5E C3 B9 ? ? ? ? E8 ? ? ? ? 48 85 C0 0F 84 ? ? ? ? 48 89 C1 48 89 F2 E8"; // TODO const SET_DITHER: &str = "E8 ? ? ? ? 84 C0 75 ? C7 43";
const SET_DITHER_ALPHA_ANIM: &str = "E8 ? ? ? ? 8B 46 ? 0F 57 C0 0F 2E C6 0F 82"; // TODO const SDK_PUBLIC_KEY_LITERAL: &str =
const SDK_PUBLIC_KEY_LITERAL: &str = "48 8B 0D ? ? ? ? 4C 89 E2 E8 ? ? ? ? 48 89 C6 48 8B 0D ? ? ? ? E8 ? ? ? ? 48 89 C7 48 8B 0D"; "48 8B 0D ? ? ? ? 4C 89 E2 E8 ? ? ? ? 48 89 C6 48 8B 0D ? ? ? ? E8 ? ? ? ? 48 89 C7 48 8B 0D";
// const HK_CHECK1: &str = "55 41 56 56 57 53 48 81 EC 00 01 00 00 48 8D AC 24 80 00 00 00 C7 45 7C 00 00 00 00"; // const HK_CHECK1: &str = "55 41 56 56 57 53 48 81 EC 00 01 00 00 48 8D AC 24 80 00 00 00 C7 45 7C 00 00 00 00";
// const HK_CHECK2: &str = "55 41 57 41 56 41 55 41 54 56 57 53 48 81 EC B8 02 00 00"; // const HK_CHECK2: &str = "55 41 57 41 56 41 55 41 54 56 57 53 48 81 EC B8 02 00 00";
@@ -18,13 +18,10 @@ const SDK_PUBLIC_KEY_LITERAL: &str = "48 8B 0D ? ? ? ? 4C 89 E2 E8 ? ? ? ? 48 89
pub struct RVAConfig { pub struct RVAConfig {
pub il2cpp_string_new_len: usize, pub il2cpp_string_new_len: usize,
pub make_initial_url: usize, pub make_initial_url: usize,
pub set_elevation_dither: usize, pub set_dither: usize,
pub set_distance_dither: usize, pub sdk_public_key: usize,
pub set_dither_alpha: usize,
pub set_dither_alpha_anim: usize,
pub hk_check1: usize, pub hk_check1: usize,
pub hk_check2: usize, pub hk_check2: usize,
pub sdk_public_key: usize,
} }
#[allow(static_mut_refs)] #[allow(static_mut_refs)]
@@ -42,12 +39,12 @@ pub static GAME_ASSEMBLY_BASE: LazyLock<usize> =
macro_rules! set_rva { macro_rules! set_rva {
($base:ident, $config:ident, $field:ident, $scan_fn:ident, $rva_pat:expr, $fallback:expr) => { ($base:ident, $config:ident, $field:ident, $scan_fn:ident, $rva_pat:expr, $fallback:expr) => {
if let Some(addr) = unsafe { $scan_fn($rva_pat) } { if let Some(addr) = unsafe { $scan_fn($rva_pat) } {
$config.$field = addr - *$base; $config.$field = addr;
println!( println!(
"[hkrpg::addr::set_rva] Found relative address for {} [{}] -> 0x{:X}", "[hkrpg::addr::set_rva] Found relative address for {} [{}] -> 0x{:X}",
stringify!($field), stringify!($field),
stringify!($base), stringify!($base),
$config.$field $config.$field - *$base
); );
} else { } else {
eprintln!( eprintln!(
@@ -85,40 +82,13 @@ pub unsafe fn init_rvas() {
0x0 0x0
); );
// set_elevation_dither // set_dither
set_rva!( set_rva!(
GAME_ASSEMBLY_BASE, GAME_ASSEMBLY_BASE,
config, config,
set_elevation_dither, set_dither,
scan_il2cpp_section, scan_il2cpp_section,
SET_ELEVATION_DITHER, SET_DITHER,
0x0
);
// set_distance_dither
set_rva!(
GAME_ASSEMBLY_BASE,
config,
set_distance_dither,
scan_il2cpp_section,
SET_DISTANCE_DITHER,
0x0
);
// set_dither_alpha
set_rva!(
GAME_ASSEMBLY_BASE,
config,
set_dither_alpha,
scan_il2cpp_section,
SET_DITHER_ALPHA,
0x0
);
// set_dither_alpha_anim
set_rva!(
GAME_ASSEMBLY_BASE,
config,
set_dither_alpha_anim,
scan_il2cpp_section,
SET_DITHER_ALPHA_ANIM,
0x0 0x0
); );

View File

@@ -1,6 +1,6 @@
use std::{ffi::CString, fmt::Display}; use std::{ffi::CString, fmt::Display};
use crate::addr::{GAME_ASSEMBLY_BASE, rva_config}; use crate::addr::rva_config;
#[repr(transparent)] #[repr(transparent)]
pub struct Il2cppString(usize); pub struct Il2cppString(usize);
@@ -15,7 +15,7 @@ impl Il2cppString {
pub fn new(string: &str) -> Self { pub fn new(string: &str) -> Self {
let func = unsafe { let func = unsafe {
std::mem::transmute::<usize, fn(*const u8, usize) -> usize>( std::mem::transmute::<usize, fn(*const u8, usize) -> usize>(
*GAME_ASSEMBLY_BASE + rva_config().il2cpp_string_new_len, rva_config().il2cpp_string_new_len,
) )
}; };
let len = string.len(); let len = string.len();

View File

@@ -2,13 +2,16 @@ use ilhook::x64::{
CallbackOption, HookFlags, HookPoint, HookType, Hooker, JmpBackRoutine, RetnRoutine, CallbackOption, HookFlags, HookPoint, HookType, Hooker, JmpBackRoutine, RetnRoutine,
}; };
#[derive(Default)]
pub struct Interceptor { pub struct Interceptor {
hooks: Vec<HookPoint>, hooks: Vec<HookPoint>,
} }
type Result<T> = std::result::Result<T, ilhook::HookError>; type Result<T> = std::result::Result<T, ilhook::HookError>;
impl Interceptor { impl Interceptor {
pub const fn new() -> Self {
Interceptor { hooks: Vec::new() }
}
pub fn attach(&mut self, addr: usize, routine: JmpBackRoutine) -> Result<()> { pub fn attach(&mut self, addr: usize, routine: JmpBackRoutine) -> Result<()> {
let hooker = Hooker::new( let hooker = Hooker::new(
addr, addr,

View File

@@ -4,7 +4,7 @@ use std::{thread, time::Duration};
use modules::{ use modules::{
HkrpgModuleManager, censorship_patch::CensorshipPatch, crypto::Crypto, hk_check::HkCheck, HkrpgModuleManager, censorship_patch::CensorshipPatch, crypto::Crypto, hk_check::HkCheck,
network::Network, misc::Misc, network::Network,
}; };
use windows::{ use windows::{
Win32::System::{Console, LibraryLoader::GetModuleHandleA}, Win32::System::{Console, LibraryLoader::GetModuleHandleA},
@@ -27,6 +27,11 @@ pub fn main() {
thread::sleep(Duration::from_millis(200)); thread::sleep(Duration::from_millis(200));
} }
let mut mm1 = HkrpgModuleManager::default();
mm1.add::<Misc>();
mm1.init()
.expect("[hkrpg::main] failed to initialize module (Misc)");
addr::init_rvas(); addr::init_rvas();
let mut module_manager = HkrpgModuleManager::default(); let mut module_manager = HkrpgModuleManager::default();

View File

@@ -11,7 +11,7 @@ macro_rules! replace {
$( $(
if $config.$field != 0 { if $config.$field != 0 {
$self.interceptor.replace( $self.interceptor.replace(
$self.base.wrapping_add($config.$field), $config.$field,
CensorshipPatch::on_set_dither, CensorshipPatch::on_set_dither,
)?; )?;
} else { } else {
@@ -24,14 +24,7 @@ macro_rules! replace {
impl HkrpgModule for HkrpgModuleContext<CensorshipPatch> { impl HkrpgModule for HkrpgModuleContext<CensorshipPatch> {
unsafe fn init(&mut self) -> Result<(), ilhook::HookError> { unsafe fn init(&mut self) -> Result<(), ilhook::HookError> {
let config = rva_config(); let config = rva_config();
replace!( replace!(self, config, set_dither);
self,
config,
set_distance_dither,
set_elevation_dither,
set_dither_alpha,
set_dither_alpha_anim
);
Ok(()) Ok(())
} }
} }

View File

@@ -1,7 +1,4 @@
use crate::{ use crate::{addr::rva_config, il2cpp_string::Il2cppString};
addr::{GAME_ASSEMBLY_BASE, rva_config},
il2cpp_string::Il2cppString,
};
use super::{HkrpgModule, HkrpgModuleContext}; use super::{HkrpgModule, HkrpgModuleContext};
@@ -14,7 +11,7 @@ impl HkrpgModule for HkrpgModuleContext<Crypto> {
let config = rva_config(); let config = rva_config();
if config.sdk_public_key != 0 { if config.sdk_public_key != 0 {
unsafe { unsafe {
*(GAME_ASSEMBLY_BASE.wrapping_add(config.sdk_public_key) as *mut Il2cppString) = *(config.sdk_public_key as *mut Il2cppString) =
Il2cppString::new(ACCOUNT_RSA_KEY_REPLACEMENT) Il2cppString::new(ACCOUNT_RSA_KEY_REPLACEMENT)
} }
println!("[crypto::init] AccountRSAKey replaced") println!("[crypto::init] AccountRSAKey replaced")

View File

@@ -10,14 +10,10 @@ impl HkrpgModule for HkrpgModuleContext<HkCheck> {
unsafe fn init(&mut self) -> Result<(), ilhook::HookError> { unsafe fn init(&mut self) -> Result<(), ilhook::HookError> {
let config = rva_config(); let config = rva_config();
if config.hk_check1 != 0 && config.hk_check2 != 0 { if config.hk_check1 != 0 && config.hk_check2 != 0 {
self.interceptor.replace( self.interceptor
self.base.wrapping_add(config.hk_check1), .replace(config.hk_check1, HkCheck::replacement)?;
HkCheck::replacement, self.interceptor
)?; .replace(config.hk_check2, HkCheck::replacement)?;
self.interceptor.replace(
self.base.wrapping_add(config.hk_check2),
HkCheck::replacement,
)?;
println!("[hk_check::init] hk_check bypassed") println!("[hk_check::init] hk_check bypassed")
} }
Ok(()) Ok(())

39
hkrpg/src/modules/misc.rs Normal file
View File

@@ -0,0 +1,39 @@
use crate::modules::{HkrpgModule, HkrpgModuleContext};
use ilhook::x64::Registers;
use std::ffi::CStr;
use windows::{
Win32::System::LibraryLoader::{GetModuleHandleA, GetProcAddress},
core::s,
};
pub struct Misc;
impl HkrpgModule for HkrpgModuleContext<Misc> {
unsafe fn init(&mut self) -> Result<(), ilhook::HookError> {
unsafe {
let ws32 = GetModuleHandleA(s!("Ws2_32.dll")).unwrap();
let get_addr_info = GetProcAddress(ws32, s!("getaddrinfo")).unwrap();
self.interceptor
.attach(get_addr_info as usize, Misc::on_get_addr_info)?;
println!("[misc::init] initialized")
}
Ok(())
}
}
impl Misc {
pub unsafe extern "win64" fn on_get_addr_info(reg: *mut Registers, _: usize) {
unsafe {
let host = CStr::from_ptr((*reg).rcx as *const i8).to_string_lossy();
if host.contains("globaldp-")
&& (host.contains("bhsr.com") || host.contains("starrails.com"))
{
println!("[*] [on_get_addr_info] {host} -> 0.0.0.0");
std::ptr::copy_nonoverlapping(c"0.0.0.0".as_ptr(), (*reg).rcx as *mut i8, 9);
}
}
}
}

View File

@@ -6,9 +6,10 @@ pub mod censorship_patch;
pub mod crypto; pub mod crypto;
pub mod hk_check; pub mod hk_check;
pub mod network; pub mod network;
pub mod misc;
pub struct HkrpgModuleContext<T> { pub struct HkrpgModuleContext<T> {
base: usize, _base: usize,
interceptor: Interceptor, interceptor: Interceptor,
_module_type: PhantomData<T>, _module_type: PhantomData<T>,
} }
@@ -16,8 +17,8 @@ pub struct HkrpgModuleContext<T> {
impl<T> HkrpgModuleContext<T> { impl<T> HkrpgModuleContext<T> {
fn new(base: usize) -> Self { fn new(base: usize) -> Self {
Self { Self {
base, _base: base,
interceptor: Interceptor::default(), interceptor: Interceptor::new(),
_module_type: PhantomData, _module_type: PhantomData,
} }
} }

View File

@@ -10,10 +10,8 @@ impl HkrpgModule for HkrpgModuleContext<Network> {
unsafe fn init(&mut self) -> Result<(), ilhook::HookError> { unsafe fn init(&mut self) -> Result<(), ilhook::HookError> {
let config = rva_config(); let config = rva_config();
if config.make_initial_url != 0 && config.il2cpp_string_new_len != 0 { if config.make_initial_url != 0 && config.il2cpp_string_new_len != 0 {
self.interceptor.attach( self.interceptor
self.base.wrapping_add(config.make_initial_url), .attach(config.make_initial_url, Network::on_make_initial_url)?;
Network::on_make_initial_url,
)?;
println!("[network::init] network patch enabled") println!("[network::init] network patch enabled")
} else { } else {
println!("[network::init] pattern is outdated! disabling http redirection") println!("[network::init] pattern is outdated! disabling http redirection")

View File

@@ -45,7 +45,8 @@ pub unsafe fn scan_il2cpp_section(pat: &str) -> Option<usize> {
match slice.get(address) { match slice.get(address) {
// jmp sub_xxxxxxx // jmp sub_xxxxxxx
Some(&0xE8) => { Some(&0xE8) => {
let offset = i32::from_le_bytes(slice[address + 1..address + 5].try_into().unwrap()); let offset =
i32::from_le_bytes(slice[address + 1..address + 5].try_into().unwrap());
GAME_ASSEMBLY_BASE.wrapping_add(address + 5 + offset as usize) GAME_ASSEMBLY_BASE.wrapping_add(address + 5 + offset as usize)
} }
// mov rcx, [rip + offset] (0x48 0x8B 0x0D XXXXXXXX) // mov rcx, [rip + offset] (0x48 0x8B 0x0D XXXXXXXX)
@@ -53,7 +54,8 @@ pub unsafe fn scan_il2cpp_section(pat: &str) -> Option<usize> {
if slice.get(address + 1) == Some(&0x8B) if slice.get(address + 1) == Some(&0x8B)
&& slice.get(address + 2) == Some(&0x0D) => && slice.get(address + 2) == Some(&0x0D) =>
{ {
let offset = i32::from_le_bytes(slice[address + 3..address + 7].try_into().unwrap()); let offset =
i32::from_le_bytes(slice[address + 3..address + 7].try_into().unwrap());
GAME_ASSEMBLY_BASE.wrapping_add(address + 7 + offset as usize) GAME_ASSEMBLY_BASE.wrapping_add(address + 7 + offset as usize)
} }
_ => GAME_ASSEMBLY_BASE.wrapping_add(address), _ => GAME_ASSEMBLY_BASE.wrapping_add(address),
@@ -89,7 +91,25 @@ pub unsafe fn scan_il2cpp_section(pat: &str) -> Option<usize> {
// pub unsafe fn scan_unity_player_section(pat: &str) -> Option<usize> { // pub unsafe fn scan_unity_player_section(pat: &str) -> Option<usize> {
// let mut slice = unsafe { unity_player_slice() }; // let mut slice = unsafe { unity_player_slice() };
// scan_first_match(&mut slice, pat) // scan_first_match(&mut slice, pat).unwrap().map(|address| {
// .unwrap() // let slice = unsafe { unity_player_slice() };
// .map(|loc| UNITY_PLAYER_BASE.wrapping_add(loc)) // match slice.get(address) {
// // jmp sub_xxxxxxx
// Some(&0xE8) => {
// let offset =
// i32::from_le_bytes(slice[address + 1..address + 5].try_into().unwrap());
// UNITY_PLAYER_BASE.wrapping_add(address + 5 + offset as usize)
// }
// // mov rcx, [rip + offset] (0x48 0x8B 0x0D XXXXXXXX)
// Some(&0x48)
// if slice.get(address + 1) == Some(&0x8B)
// && slice.get(address + 2) == Some(&0x0D) =>
// {
// let offset =
// i32::from_le_bytes(slice[address + 3..address + 7].try_into().unwrap());
// UNITY_PLAYER_BASE.wrapping_add(address + 7 + offset as usize)
// }
// _ => UNITY_PLAYER_BASE.wrapping_add(address),
// }
// })
// } // }

View File

@@ -4,16 +4,17 @@ use std::ffi::CString;
use std::ptr::null_mut; use std::ptr::null_mut;
use windows::Win32::Foundation::{CloseHandle, GetLastError, HANDLE}; use windows::Win32::Foundation::{CloseHandle, GetLastError, HANDLE};
use windows::Win32::Security::{GetTokenInformation, TOKEN_ELEVATION, TOKEN_QUERY, TokenElevation};
use windows::Win32::System::Diagnostics::Debug::WriteProcessMemory; use windows::Win32::System::Diagnostics::Debug::WriteProcessMemory;
use windows::Win32::System::LibraryLoader::{GetModuleHandleA, GetProcAddress}; use windows::Win32::System::LibraryLoader::{GetModuleHandleA, GetProcAddress};
use windows::Win32::System::Memory::{ use windows::Win32::System::Memory::{
MEM_COMMIT, MEM_RELEASE, MEM_RESERVE, PAGE_READWRITE, VirtualAllocEx, VirtualFreeEx, MEM_COMMIT, MEM_RELEASE, MEM_RESERVE, PAGE_READWRITE, VirtualAllocEx, VirtualFreeEx,
}; };
use windows::Win32::System::Threading::{ use windows::Win32::System::Threading::{
CREATE_SUSPENDED, CreateProcessA, CreateRemoteThread, PROCESS_INFORMATION, ResumeThread, CREATE_SUSPENDED, CreateProcessA, CreateRemoteThread, OpenProcessToken, PROCESS_INFORMATION,
STARTUPINFOA, WaitForSingleObject, ResumeThread, STARTUPINFOA, WaitForSingleObject,
}; };
use windows::core::{PSTR, s}; use windows::core::{Error, PSTR, s};
fn inject_standard(h_target: HANDLE, dll_path: &str) -> bool { fn inject_standard(h_target: HANDLE, dll_path: &str) -> bool {
unsafe { unsafe {
@@ -70,7 +71,48 @@ fn inject_standard(h_target: HANDLE, dll_path: &str) -> bool {
} }
} }
fn is_running_as_admin() -> Result<bool, Error> {
unsafe {
let mut token_handle = HANDLE::default();
let current_process = windows::Win32::System::Threading::GetCurrentProcess();
if OpenProcessToken(current_process, TOKEN_QUERY, &mut token_handle).is_err() {
return Err(windows::core::Error::from_win32());
}
let mut elevation = TOKEN_ELEVATION::default();
let mut size = std::mem::size_of::<TOKEN_ELEVATION>() as u32;
let success = GetTokenInformation(
token_handle,
TokenElevation,
Some(&mut elevation as *mut _ as *mut _),
size,
&mut size,
);
let _ = CloseHandle(token_handle);
if success.is_ok() {
Ok(elevation.TokenIsElevated != 0)
} else {
Err(windows::core::Error::from_win32())
}
}
}
fn wait_exit() {
println!("Press any key to exit...");
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
}
fn main() { fn main() {
if !is_running_as_admin().unwrap_or_default() {
println!("launcher need to be launched as admin");
wait_exit();
}
let current_dir = std::env::current_dir().unwrap(); let current_dir = std::env::current_dir().unwrap();
let dll_path = current_dir.join("hkrpg.dll"); let dll_path = current_dir.join("hkrpg.dll");
if !dll_path.is_file() { if !dll_path.is_file() {