initial commit

This commit is contained in:
amizing25
2025-03-14 14:49:05 +07:00
commit 8861b07cb8
22 changed files with 1075 additions and 0 deletions

9
hkrpg/Cargo.toml Normal file
View File

@@ -0,0 +1,9 @@
[package]
name = "hkrpg"
edition = "2024"
version.workspace = true
[dependencies]
windows.workspace = true
ilhook.workspace = true
patternscan.workspace = true

117
hkrpg/src/addr.rs Normal file
View File

@@ -0,0 +1,117 @@
use std::sync::{LazyLock, OnceLock};
use windows::{Win32::System::LibraryLoader::GetModuleHandleA, core::s};
use crate::util::{scan_il2cpp_section, scan_unity_player_section};
const PTR_TO_STRING_ANSI: &str = "E8 ? ? ? ? 48 ? ? 48 85 C0 75 ? 48 8D 4C 24";
const MAKE_INITIAL_URL: &str = "55 41 56 56 57 53 48 83 EC ? 48 8D 6C 24 ? 48 C7 45 ? ? ? ? ? 48 89 D6 48 89 CF E8 ? ? ? ? 84 C0"; // TODO
const SET_ELEVATION_DITHER: &str = "E9 ? ? ? ? 0F 28 74 24 ? 48 83 C4 ? 5B 5F 5E 41 5E 41 5F C3 31 F6"; // TODO
const SET_DISTANCE_DITHER: &str = "E8 ? ? ? ? 49 8B 46 ? 48 85 C0 0F 84 ? ? ? ? 48 8B 4D"; // TODO
const SET_DITHER_ALPHA: &str = "56 57 48 83 EC ? 0F 29 74 24 ? 44 89 C6 0F 28 F1 48 89 CF 80 3D ? ? ? ? ? 75 ? 80 7F"; // TODO
const SET_DITHER_ALPHA_ANIM: &str = "56 57 55 53 48 83 EC ? 44 0F 29 44 24 ? 0F 29 7C 24 ? 0F 29 74 24 ? 44 0F 28 C3 0F 28 F2 0F 28 F9"; // TODO
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";
#[derive(Default)]
pub struct RVAConfig {
pub ptr_to_string_ansi: usize,
pub make_initial_url: usize,
pub set_elevation_dither: usize,
pub set_distance_dither: usize,
pub set_dither_alpha: usize,
pub set_dither_alpha_anim: usize,
pub hk_check1: usize,
pub hk_check2: usize,
}
#[allow(static_mut_refs)]
pub fn rva_config() -> &'static mut RVAConfig {
static mut RVA_CONFIG: OnceLock<RVAConfig> = OnceLock::new();
unsafe { RVA_CONFIG.get_mut_or_init(RVAConfig::default) }
}
pub static GAME_ASSEMBLY_BASE: LazyLock<usize> =
LazyLock::new(|| unsafe { GetModuleHandleA(s!("GameAssembly.dll")).unwrap().0 as usize });
pub static UNITY_PLAYER_BASE: LazyLock<usize> =
LazyLock::new(|| unsafe { GetModuleHandleA(s!("UnityPlayer.dll")).unwrap().0 as usize });
macro_rules! set_rva {
($config:ident, $field:ident, $scan_fn:ident, $rva_pat:expr, $fallback:expr) => {
if let Some(addr) = unsafe { $scan_fn($rva_pat) } {
$config.$field = addr - *GAME_ASSEMBLY_BASE;
println!(
"[hkrpg::addr::set_rva] Found relative address for {} -> 0x{:X}",
stringify!($field),
$config.$field
);
} else {
eprintln!(
"[hkrpg::addr::set_rva] Failed to find pattern for {} using {}",
stringify!($field),
stringify!($scan_fn)
);
$config.$field = $fallback
}
};
}
pub unsafe fn init_rvas() {
let config = rva_config();
// ptr_to_string_ansi
set_rva!(
config,
ptr_to_string_ansi,
scan_il2cpp_section,
PTR_TO_STRING_ANSI,
0x0
);
// make_initial_url
set_rva!(
config,
make_initial_url,
scan_il2cpp_section,
MAKE_INITIAL_URL,
0x0
);
// set_elevation_dither
set_rva!(
config,
set_elevation_dither,
scan_il2cpp_section,
SET_ELEVATION_DITHER,
0x0
);
// set_distance_dither
set_rva!(
config,
set_distance_dither,
scan_il2cpp_section,
SET_DISTANCE_DITHER,
0x0
);
// set_dither_alpha
set_rva!(
config,
set_dither_alpha,
scan_il2cpp_section,
SET_DITHER_ALPHA,
0x0
);
// set_dither_alpha_anim
set_rva!(
config,
set_dither_alpha_anim,
scan_il2cpp_section,
SET_DITHER_ALPHA_ANIM,
0x0
);
set_rva!(config, hk_check1, scan_unity_player_section, HK_CHECK1, 0x0);
set_rva!(config, hk_check2, scan_unity_player_section, HK_CHECK2, 0x0);
}

View File

@@ -0,0 +1,43 @@
use std::{ffi::CString, fmt::Display};
use crate::addr::{GAME_ASSEMBLY_BASE, rva_config};
#[repr(transparent)]
pub struct Il2cppString(usize);
impl From<u64> for Il2cppString {
fn from(value: u64) -> Self {
Self(value as usize)
}
}
impl Il2cppString {
pub fn new(string: &str) -> Self {
let func = unsafe {
std::mem::transmute::<usize, fn(*const u8, usize) -> usize>(
*GAME_ASSEMBLY_BASE + rva_config().ptr_to_string_ansi,
)
};
let len = string.len();
let string = CString::new(string).unwrap();
let string = string.as_c_str();
Self(func(string.to_bytes_with_nul().as_ptr(), len))
}
pub fn raw(&self) -> u64 {
self.0 as u64
}
}
impl Display for Il2cppString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str_length = unsafe { *(self.0.wrapping_add(16) as *const u32) };
let str_ptr = self.0.wrapping_add(20) as *const u8;
let slice = unsafe { std::slice::from_raw_parts(str_ptr, (str_length * 2) as usize) };
write!(
f,
"{}",
String::from_utf16le(slice).map_err(|_| std::fmt::Error)?
)
}
}

41
hkrpg/src/interceptor.rs Normal file
View File

@@ -0,0 +1,41 @@
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 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 = unsafe { hooker.hook() }?;
self.hooks.push(hook_point);
Ok(())
}
pub 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 = unsafe { hooker.hook() }?;
self.hooks.push(hook_point);
Ok(())
}
}

41
hkrpg/src/lib.rs Normal file
View File

@@ -0,0 +1,41 @@
#![feature(str_from_utf16_endian, once_cell_get_mut)]
use std::{thread, time::Duration};
use modules::{
HkrpgModuleManager, censorship_patch::CensorshipPatch, hk_check::HkCheck, network::Network,
};
use windows::{
Win32::System::{Console, LibraryLoader::GetModuleHandleA},
core::s,
};
mod addr;
mod il2cpp_string;
mod interceptor;
mod modules;
mod util;
pub fn main() {
unsafe {
let _ = Console::AllocConsole();
println!("[hkrpg::main] init");
while GetModuleHandleA(s!("GameAssembly.dll")).is_err() {
thread::sleep(Duration::from_millis(200));
}
addr::init_rvas();
let mut module_manager = HkrpgModuleManager::default();
module_manager.add::<HkCheck>();
module_manager.add::<Network>();
module_manager.add::<CensorshipPatch>();
module_manager
.init()
.expect("[hkrpg::main] failed to initialize modules");
thread::sleep(Duration::from_secs(u64::MAX));
}
}

View File

@@ -0,0 +1,43 @@
use ilhook::x64::Registers;
use crate::addr::rva_config;
use super::{HkrpgModule, HkrpgModuleContext};
pub struct CensorshipPatch;
macro_rules! replace {
($self:ident, $config:ident, $($field:ident),*) => {
$(
if $config.$field != 0 {
$self.interceptor.replace(
$self.base.wrapping_add($config.$field),
CensorshipPatch::on_set_dither,
)?;
} else {
println!("[censorship_patch::init] pattern is outdated! disabling dither patch for {}", stringify!($field));
}
)*
};
}
impl HkrpgModule for HkrpgModuleContext<CensorshipPatch> {
unsafe fn init(&mut self) -> Result<(), ilhook::HookError> {
let config = rva_config();
replace!(
self,
config,
set_distance_dither,
set_elevation_dither,
set_dither_alpha,
set_dither_alpha_anim
);
Ok(())
}
}
impl CensorshipPatch {
pub unsafe extern "win64" fn on_set_dither(_: *mut Registers, _: usize, _: usize) -> usize {
0
}
}

View File

@@ -0,0 +1,30 @@
use ilhook::x64::Registers;
use crate::addr::rva_config;
use super::{HkrpgModule, HkrpgModuleContext};
pub struct HkCheck;
impl HkrpgModule for HkrpgModuleContext<HkCheck> {
unsafe fn init(&mut self) -> Result<(), ilhook::HookError> {
let config = rva_config();
if config.hk_check1 != 0 && config.hk_check2 != 0 {
self.interceptor.replace(
self.base.wrapping_add(config.hk_check1),
HkCheck::replacement,
)?;
self.interceptor.replace(
self.base.wrapping_add(config.hk_check2),
HkCheck::replacement,
)?;
}
Ok(())
}
}
impl HkCheck {
extern "win64" fn replacement(_: *mut Registers, _: usize, _: usize) -> usize {
0
}
}

51
hkrpg/src/modules/mod.rs Normal file
View File

@@ -0,0 +1,51 @@
use std::marker::PhantomData;
use crate::{addr, interceptor::Interceptor};
pub mod censorship_patch;
pub mod hk_check;
pub mod network;
pub struct HkrpgModuleContext<T> {
base: usize,
interceptor: Interceptor,
_module_type: PhantomData<T>,
}
impl<T> HkrpgModuleContext<T> {
fn new(base: usize) -> Self {
Self {
base,
interceptor: Interceptor::default(),
_module_type: PhantomData,
}
}
}
pub trait HkrpgModule {
unsafe fn init(&mut self) -> Result<(), ilhook::HookError>;
}
#[derive(Default)]
pub struct HkrpgModuleManager {
modules: Vec<Box<dyn HkrpgModule>>,
}
impl HkrpgModuleManager {
pub fn add<T: 'static>(&mut self)
where
HkrpgModuleContext<T>: HkrpgModule,
{
self.modules.push(Box::new(HkrpgModuleContext::<T>::new(
*addr::GAME_ASSEMBLY_BASE,
)));
}
pub unsafe fn init(&mut self) -> Result<(), ilhook::HookError> {
for module in self.modules.iter_mut() {
unsafe { module.init() }?;
}
Ok(())
}
}

View File

@@ -0,0 +1,60 @@
use ilhook::x64::Registers;
use crate::{addr::rva_config, il2cpp_string::Il2cppString};
use super::{HkrpgModule, HkrpgModuleContext};
pub struct Network;
impl HkrpgModule for HkrpgModuleContext<Network> {
unsafe fn init(&mut self) -> Result<(), ilhook::HookError> {
let config = rva_config();
if config.make_initial_url != 0 && config.ptr_to_string_ansi != 0 {
self.interceptor.attach(
self.base.wrapping_add(config.make_initial_url),
Network::on_make_initial_url,
)?;
} else {
println!("[network::init] pattern is outdated! disabling http redirection")
}
Ok(())
}
}
impl Network {
const SDK_URL: &str = "http://127.0.0.1:21000";
// const DISPATCH_URL: &str = "http://127.0.0.1:21000";
const REDIRECT_SDK: bool = true;
const REDIRECT_DISPATCH: bool = true;
extern "win64" fn on_make_initial_url(reg: *mut Registers, _: usize) {
let url = unsafe { Il2cppString::from((*reg).rcx).to_string() };
let mut new_url = match &url {
s if (s.contains("mihoyo.com") || s.contains("hoyoverse.com"))
&& Self::REDIRECT_SDK =>
{
Self::SDK_URL.to_string()
}
s if (s.contains("bhsr.com") || s.contains("starrails.com"))
&& Self::REDIRECT_DISPATCH =>
{
Self::SDK_URL.to_string()
}
s => {
println!("Leaving request as-is: {s}");
return;
}
};
url.split('/').skip(3).for_each(|s| {
new_url.push('/');
new_url.push_str(s);
});
println!("UnityWebRequest: \"{url}\", replacing with \"{new_url}\"");
unsafe {
(*reg).rcx = Il2cppString::new(&new_url).raw() as u64;
}
}
}

86
hkrpg/src/util.rs Normal file
View File

@@ -0,0 +1,86 @@
use std::cell::OnceCell;
use patternscan::scan_first_match;
use windows::{
Win32::System::{
LibraryLoader::GetModuleHandleA,
ProcessStatus::{GetModuleInformation, MODULEINFO},
Threading::GetCurrentProcess,
},
core::s,
};
use crate::addr::{GAME_ASSEMBLY_BASE, UNITY_PLAYER_BASE};
#[allow(static_mut_refs)]
unsafe fn game_assembly_slice() -> &'static [u8] {
static mut SLICE: OnceCell<&[u8]> = OnceCell::new();
unsafe {
SLICE.get_or_init(|| {
let module = GetModuleHandleA(s!("GameAssembly.dll")).unwrap();
let mut module_info = MODULEINFO {
lpBaseOfDll: std::ptr::null_mut(),
SizeOfImage: 0,
EntryPoint: std::ptr::null_mut(),
};
GetModuleInformation(
GetCurrentProcess(),
module,
&mut module_info,
std::mem::size_of::<MODULEINFO>() as u32,
)
.unwrap();
std::slice::from_raw_parts(
module.0 as *const u8,
module_info.SizeOfImage.try_into().unwrap(),
)
})
}
}
#[allow(static_mut_refs)]
unsafe fn unity_player_slice() -> &'static [u8] {
static mut SLICE: OnceCell<&[u8]> = OnceCell::new();
unsafe {
SLICE.get_or_init(|| {
let module = GetModuleHandleA(s!("UnityPlayer.dll")).unwrap();
let mut module_info = MODULEINFO {
lpBaseOfDll: std::ptr::null_mut(),
SizeOfImage: 0,
EntryPoint: std::ptr::null_mut(),
};
GetModuleInformation(
GetCurrentProcess(),
module,
&mut module_info,
std::mem::size_of::<MODULEINFO>() as u32,
)
.unwrap();
std::slice::from_raw_parts(
module.0 as *const u8,
module_info.SizeOfImage.try_into().unwrap(),
)
})
}
}
pub unsafe fn scan_il2cpp_section(pat: &str) -> Option<usize> {
let mut slice = unsafe { game_assembly_slice() };
scan_first_match(&mut slice, pat).unwrap().map(|address| {
let slice = unsafe { game_assembly_slice() };
let instruction = &slice[address..address + 1][0];
if *instruction == 0xE8 {
let offset = i32::from_le_bytes((&slice[address + 1..address + 1 + 4]).try_into().unwrap());
let pointer = offset as usize + 5 + address;
return GAME_ASSEMBLY_BASE.wrapping_add(pointer);
}
GAME_ASSEMBLY_BASE.wrapping_add(address)
})
}
pub unsafe fn scan_unity_player_section(pat: &str) -> Option<usize> {
let mut slice = unsafe { unity_player_slice() };
scan_first_match(&mut slice, pat)
.unwrap()
.map(|loc| UNITY_PLAYER_BASE.wrapping_add(loc))
}