10 Commits

Author SHA1 Message Date
amizing25 62a934011b feat: add gitea prebuilt actions
Prebuilt release / prebuilt (push) Successful in 1m42s
2026-07-16 22:59:49 +07:00
amizing25 b559c3b433 chore: remove mhypbase 2026-07-16 22:45:38 +07:00
amizing25 8ed8c7c443 feat: update pattern 2026-07-16 16:56:23 +07:00
amizing25 1ec60097a6 feat: add AccountPlatNative patch for CN SDK auth 2026-06-03 18:47:17 +07:00
amizing25 aec2a1e575 feat(launcher): build executable to require administrator by default
Add rustc-link-arg directives in build.rs to embed a UAC manifest:
- /MANIFEST:EMBED
- /MANIFESTUAC:level='requireAdministrator'
2025-08-22 06:39:39 +07:00
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
24 changed files with 582 additions and 166 deletions
+99
View File
@@ -0,0 +1,99 @@
name: Prebuilt release
on:
push:
branches:
- main
workflow_dispatch:
permissions:
contents: read
releases: write
jobs:
prebuilt:
runs-on: ubuntu-latest
env:
CARGO_TERM_COLOR: always
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Install nightly Rust
uses: dtolnay/rust-toolchain@nightly
with:
targets: x86_64-pc-windows-gnu
- name: Install MinGW and packaging tools
run: |
sudo apt-get update
sudo apt-get install -y mingw-w64 zip unzip jq
- name: Build release binaries
env:
CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER: x86_64-w64-mingw32-gcc
run: cargo build --release --target x86_64-pc-windows-gnu -p ext -p launcher
- name: Package prebuilt release
run: |
set -euo pipefail
rm -rf dist
mkdir -p dist
cp target/x86_64-pc-windows-gnu/release/hkrpg.dll dist/
cp target/x86_64-pc-windows-gnu/release/launcher.exe dist/
test -f dist/hkrpg.dll
test -f dist/launcher.exe
(
cd dist
zip -9 -q ../hkrpg.zip hkrpg.dll launcher.exe
)
unzip -l hkrpg.zip
- name: Publish prebuilt release
env:
GITEA_API_URL: ${{ gitea.server_url }}/api/v1
GITEA_REPOSITORY: ${{ gitea.repository }}
GITEA_SHA: ${{ gitea.sha }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
set -euo pipefail
api="${GITEA_API_URL}"
repo="${GITEA_REPOSITORY}"
tag="prebuilt"
auth=(-H "Authorization: token ${GITEA_TOKEN}" -H "Accept: application/json")
release_status="$(curl -sS "${auth[@]}" -o release.json -w '%{http_code}' \
"${api}/repos/${repo}/releases/tags/${tag}" || true)"
case "${release_status}" in
200)
;;
404)
curl -fsS -X POST "${auth[@]}" \
-H "Content-Type: application/json" \
-d "{\"tag_name\":\"${tag}\",\"target_commitish\":\"${GITEA_SHA}\",\"name\":\"${tag}\",\"body\":\"Automated prebuilt binaries for ${GITEA_SHA}\",\"draft\":false,\"prerelease\":false}" \
"${api}/repos/${repo}/releases" > release.json
;;
*)
cat release.json
exit 1
;;
esac
release_id="$(jq -er '.id' release.json)"
assets="$(curl -fsS "${auth[@]}" \
"${api}/repos/${repo}/releases/${release_id}/assets")"
while read -r asset_id; do
test -z "${asset_id}" || curl -fsS -X DELETE "${auth[@]}" \
"${api}/repos/${repo}/releases/${release_id}/assets/${asset_id}"
done < <(printf '%s' "${assets}" | jq -r '.[] | select(.name == "hkrpg.zip") | .id')
curl -fsS -X POST "${auth[@]}" \
-F "attachment=@hkrpg.zip;type=application/zip" \
"${api}/repos/${repo}/releases/${release_id}/assets?name=hkrpg.zip"
Generated
+84 -4
View File
@@ -17,6 +17,32 @@ version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "dashmap"
version = "6.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
dependencies = [
"cfg-if",
"crossbeam-utils",
"hashbrown",
"lock_api",
"once_cell",
"parking_lot_core",
]
[[package]]
name = "ext"
version = "0.1.0"
@@ -25,10 +51,17 @@ dependencies = [
"windows",
]
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "hkrpg"
version = "0.1.0"
dependencies = [
"dashmap",
"ilhook",
"patternscan",
"windows",
@@ -77,6 +110,15 @@ version = "0.2.171"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]]
name = "memchr"
version = "2.7.4"
@@ -84,11 +126,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "mhypbase"
version = "0.1.0"
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "parking_lot_core"
version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"hkrpg",
"windows",
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-link",
]
[[package]]
@@ -115,6 +168,15 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.11.1"
@@ -144,6 +206,18 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "syn"
version = "2.0.100"
@@ -201,6 +275,12 @@ dependencies = [
"windows-targets",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-result"
version = "0.1.2"
+8 -1
View File
@@ -1,11 +1,17 @@
[workspace]
members = ["hkrpg", "launcher", "mhypbase", "ext"]
members = ["hkrpg", "launcher", "ext"]
resolver = "2"
[workspace.package]
version = "0.1.0"
[profile.release]
opt-level = "z"
lto = true
codegen-units = 1
strip = true
[workspace.dependencies]
windows = { version = "0.54.0", features = [
"Win32_Foundation",
@@ -22,6 +28,7 @@ windows = { version = "0.54.0", features = [
] }
ilhook = "2.1.1"
patternscan = "1.2.0"
dashmap = "6.1.0"
# Local crates
hkrpg = { path = "hkrpg/" }
+2 -2
View File
@@ -3,9 +3,9 @@ features:
- remove censorship
- replace AccountRSAKey into custom public key (by default, compatible with [hoyo-sdk by xeondev](https://git.xeondev.com/reversedrooms/hoyo-sdk))
currently, this has only been tested on CNBETAWin3.1.53 and may require an update for future versions.
currently, this has only been tested on CNBETAWin4.4.51 and may require an update for future versions.
note: if you plan to use `mhypbase.dll`, please make a copy of the original first in case something breaks. Otherwise, simply copy `hkrpg.dll` and `launcher.exe` into the game folder and run `launcher.exe` as an administrator.
note: simply copy `hkrpg.dll` and `launcher.exe` into the game folder and run `launcher.exe` as an administrator.
Sources:
+1
View File
@@ -7,3 +7,4 @@ version.workspace = true
windows.workspace = true
ilhook.workspace = true
patternscan.workspace = true
dashmap.workspace = true
+9
View File
@@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAudOyZJDKXjYQdUS+wN7d
lKBP2XkhkDO/sx6pttn719o+rJ8y8XOO3rftSoRasvw1nzidDQqEnHl1Aw5+a90D
OQvlgBaIda3npqVmi4X2sEzswR7EH5BOJizx7LAxixcoby3jOQathdpl44wQ04XR
+SIkukvxcL1jTepfuc0Pp+DD+4MU5O4/38b9v+ryizU+1CdwzukV27rkMvOtA6ok
Is6Vkb5ubcAC/W3vCIP5gv+QRdYB6ihzLhs5WI/9Ex2PrLpdpxr7gClvEFxkHcLy
LCvSfp9pvt/tnsYsk1tct0HKEr8tlaJ6t1zEGEvFQZRpBsahl/lhpT15vyM8iyiD
DwIDAQAB
-----END PUBLIC KEY-----
+23 -64
View File
@@ -4,27 +4,22 @@ use windows::{Win32::System::LibraryLoader::GetModuleHandleA, core::s};
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 MAKE_INITIAL_URL: &str = "E8 ? ? ? ? 48 89 D9 48 89 C2 E8 ? ? ? ? 48 89 D9 4C 89 FA E8 ? ? ? ? 49 89 5D"; // TODO
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 SET_DISTANCE_DITHER: &str = "E8 ? ? ? ? 49 8B 46 ? 48 85 C0 0F 84 ? ? ? ? 48 8B 4D"; // 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_ALPHA_ANIM: &str = "E8 ? ? ? ? 8B 46 ? 0F 57 C0 0F 2E C6 0F 82"; // TODO
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";
// 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 IL2CPP_STRING_NEW_LEN: &str =
"E8 ? ? ? ? EB ? 31 C0 48 89 06 48 8B 47 ? 48 89 46 ? F2 0F 10 47";
const MAKE_INITIAL_URL: &str =
"55 41 56 56 57 53 48 81 EC 90 00 00 00 48 8D AC 24 ? ? ? ? 48 C7 45 ? ? ? ? ? 48 85 C9 74";
const SET_DITHER: &str = "E8 ? ? ? ? 84 C0 75 ? C7 43";
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";
#[derive(Default)]
pub struct RVAConfig {
pub il2cpp_string_new_len: 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 set_dither: usize,
pub sdk_public_key: usize,
pub hk_check1: usize,
pub hk_check2: usize,
pub sdk_public_key: usize,
}
#[allow(static_mut_refs)]
@@ -41,13 +36,21 @@ pub static GAME_ASSEMBLY_BASE: LazyLock<usize> =
macro_rules! set_rva {
($base:ident, $config:ident, $field:ident, $scan_fn:ident, $rva_pat:expr, $fallback:expr) => {
if let Some(addr) = unsafe { $scan_fn($rva_pat) } {
$config.$field = addr - *$base;
if $rva_pat == "" {
$config.$field = *$base + $fallback;
println!(
"[hkrpg::addr::set_rva] Using fallback relative address for {} [{}] -> 0x{:X}",
stringify!($field),
stringify!($base),
$config.$field - *$base
);
} else if let Some(addr) = unsafe { $scan_fn($rva_pat) } {
$config.$field = addr;
println!(
"[hkrpg::addr::set_rva] Found relative address for {} [{}] -> 0x{:X}",
stringify!($field),
stringify!($base),
$config.$field
$config.$field - *$base
);
} else {
eprintln!(
@@ -85,40 +88,13 @@ pub unsafe fn init_rvas() {
0x0
);
// set_elevation_dither
// set_dither
set_rva!(
GAME_ASSEMBLY_BASE,
config,
set_elevation_dither,
set_dither,
scan_il2cpp_section,
SET_ELEVATION_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,
SET_DITHER,
0x0
);
@@ -131,21 +107,4 @@ pub unsafe fn init_rvas() {
SDK_PUBLIC_KEY_LITERAL,
0x0
)
// set_rva!(
// UNITY_PLAYER_BASE,
// config,
// hk_check1,
// scan_unity_player_section,
// HK_CHECK1,
// 0x0
// );
// set_rva!(
// UNITY_PLAYER_BASE,
// config,
// hk_check2,
// scan_unity_player_section,
// HK_CHECK2,
// 0x0
// );
}
+2 -2
View File
@@ -1,6 +1,6 @@
use std::{ffi::CString, fmt::Display};
use crate::addr::{GAME_ASSEMBLY_BASE, rva_config};
use crate::addr::rva_config;
#[repr(transparent)]
pub struct Il2cppString(usize);
@@ -15,7 +15,7 @@ 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().il2cpp_string_new_len,
rva_config().il2cpp_string_new_len,
)
};
let len = string.len();
+4 -1
View File
@@ -2,13 +2,16 @@ 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 const fn new() -> Self {
Interceptor { hooks: Vec::new() }
}
pub fn attach(&mut self, addr: usize, routine: JmpBackRoutine) -> Result<()> {
let hooker = Hooker::new(
addr,
+9 -3
View File
@@ -1,10 +1,10 @@
#![feature(str_from_utf16_endian, once_cell_get_mut)]
#![feature(once_cell_get_mut)]
use std::{thread, time::Duration};
use modules::{
HkrpgModuleManager, censorship_patch::CensorshipPatch, crypto::Crypto, hk_check::HkCheck,
network::Network,
HkrpgModuleManager, apn::ApnPatch, censorship_patch::CensorshipPatch, crypto::Crypto,
hk_check::HkCheck, misc::Misc, network::Network,
};
use windows::{
Win32::System::{Console, LibraryLoader::GetModuleHandleA},
@@ -27,9 +27,15 @@ pub fn main() {
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();
let mut module_manager = HkrpgModuleManager::default();
module_manager.add::<ApnPatch>();
module_manager.add::<HkCheck>();
module_manager.add::<Network>();
module_manager.add::<Crypto>();
+160
View File
@@ -0,0 +1,160 @@
use dashmap::DashMap;
use ilhook::x64::Registers;
use patternscan::scan_first_match;
use std::{
borrow::Cow,
ffi::{CStr, CString},
path::Path,
sync::{LazyLock, OnceLock},
};
use windows::{
Win32::System::{
LibraryLoader::{GetModuleHandleW, LoadLibraryW},
ProcessStatus::{GetModuleInformation, MODULEINFO},
Threading::GetCurrentProcess,
},
core::{PCWSTR, w},
};
use crate::modules::{HkrpgModule, HkrpgModuleContext};
pub struct ApnPatch;
impl HkrpgModule for HkrpgModuleContext<ApnPatch> {
unsafe fn init(&mut self) -> Result<(), ilhook::HookError> {
if !load_apn_dll() {
return Ok(());
};
let base;
loop {
if let Ok(module) = unsafe { GetModuleHandleW(w!("AccountPlatNative.dll")) } {
base = module.0 as usize;
println!("AccountPlatNative.dll: 0x{base:X}");
break;
}
std::thread::sleep(std::time::Duration::from_millis(50));
}
let Some(string_alloc_rva) = scan_apn("E8 ? ? ? ? 41 89 BF") else {
println!("[AccountPlatNative] failed to scan string_alloc rva. patch disabled");
return Ok(());
};
self.interceptor
.attach(base + string_alloc_rva, on_string_alloc)
.expect("Failed to hook AccountPlatNative string alloc");
println!("[AccountPlatNative] patch enabled");
Ok(())
}
}
extern "win64" fn on_string_alloc(reg: *mut Registers, _: usize) {
const SDK_PUBLIC_KEY: &str = include_str!("../../sdk_public_key.pem");
static STRING_CACHE_MAP: LazyLock<DashMap<Cow<'static, str>, CString>> =
LazyLock::new(DashMap::new);
unsafe {
let content = CStr::from_ptr((*reg).rdx as *const i8).to_string_lossy();
let size = (*reg).r8;
if let Some(cstring) = STRING_CACHE_MAP.get(&content) {
(*reg).rdx = cstring.as_ptr() as u64;
(*reg).r8 = cstring.count_bytes() as u64;
return;
}
// URL string alloc
if content.starts_with("https://") && content.contains(".mihoyo.com") {
let mut new_url = String::from("http://127.0.0.1:21000");
content.split('/').skip(3).for_each(|s| {
new_url.push('/');
new_url.push_str(s);
});
let cstring = STRING_CACHE_MAP
.entry(content.clone())
.or_insert_with(|| CString::new(&*new_url).expect("Failed to create CString"));
(*reg).rdx = cstring.as_ptr() as u64;
(*reg).r8 = new_url.len() as u64;
println!("[AccountPlatNative] url {content} replaced to {new_url}");
}
// RSA public key string alloc
else if size == 268 && content.starts_with("-----BEGIN PUBLIC KEY-----") {
let cstring = STRING_CACHE_MAP
.entry(content.clone())
.or_insert_with(|| CString::new(SDK_PUBLIC_KEY).expect("Failed to create CString"));
(*reg).rdx = cstring.as_ptr() as u64;
(*reg).r8 = SDK_PUBLIC_KEY.len() as u64;
println!("[AccountPlatNative] sdk public key replaced! ");
}
}
}
fn load_apn_dll() -> bool {
let dll_path = "./StarRail_Data/Plugins/x86_64/AccountPlatNative.dll";
if !Path::new(dll_path).exists() {
return false;
}
let wide_dll_path: Vec<u16> = dll_path.encode_utf16().chain(std::iter::once(0)).collect();
let ptr = wide_dll_path.as_ptr();
if let Err(err) = unsafe { LoadLibraryW(PCWSTR::from_raw(ptr)) } {
println!("[AccountPlatNative] Failed to inject {dll_path}. Error: {err:#?}",);
};
true
}
pub fn scan_apn(pat: &str) -> Option<usize> {
let mut slice = apn_slice();
scan_first_match(&mut slice, pat).unwrap().map(|address| {
let slice = apn_slice();
match slice.get(address) {
// jmp sub_xxxxxxx
Some(&0xE8) => {
let offset =
i32::from_le_bytes(slice[address + 1..address + 5].try_into().unwrap());
address + 5 + offset as usize
}
// mov REGISTER, [rip + offset] (0x48 0x8B 0x0D XXXXXXXX)
Some(&0x48) if slice.get(address + 1) == Some(&0x8B) => {
let offset =
i32::from_le_bytes(slice[address + 3..address + 7].try_into().unwrap());
address + 7 + offset as usize
}
_ => address,
}
})
}
fn apn_slice() -> &'static [u8] {
static SLICE: OnceLock<&[u8]> = OnceLock::new();
unsafe {
SLICE.get_or_init(|| {
let module = GetModuleHandleW(w!("AccountPlatNative.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(),
)
})
}
}
+2 -9
View File
@@ -11,7 +11,7 @@ macro_rules! replace {
$(
if $config.$field != 0 {
$self.interceptor.replace(
$self.base.wrapping_add($config.$field),
$config.$field,
CensorshipPatch::on_set_dither,
)?;
} else {
@@ -24,14 +24,7 @@ macro_rules! replace {
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
);
replace!(self, config, set_dither);
Ok(())
}
}
+2 -5
View File
@@ -1,7 +1,4 @@
use crate::{
addr::{GAME_ASSEMBLY_BASE, rva_config},
il2cpp_string::Il2cppString,
};
use crate::{addr::rva_config, il2cpp_string::Il2cppString};
use super::{HkrpgModule, HkrpgModuleContext};
@@ -14,7 +11,7 @@ impl HkrpgModule for HkrpgModuleContext<Crypto> {
let config = rva_config();
if config.sdk_public_key != 0 {
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)
}
println!("[crypto::init] AccountRSAKey replaced")
+4 -8
View File
@@ -10,14 +10,10 @@ 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,
)?;
self.interceptor
.replace(config.hk_check1, HkCheck::replacement)?;
self.interceptor
.replace(config.hk_check2, HkCheck::replacement)?;
println!("[hk_check::init] hk_check bypassed")
}
Ok(())
+39
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);
}
}
}
}
+5 -3
View File
@@ -2,13 +2,15 @@ use std::marker::PhantomData;
use crate::{addr, interceptor::Interceptor};
pub mod apn;
pub mod censorship_patch;
pub mod crypto;
pub mod hk_check;
pub mod misc;
pub mod network;
pub struct HkrpgModuleContext<T> {
base: usize,
_base: usize,
interceptor: Interceptor,
_module_type: PhantomData<T>,
}
@@ -16,8 +18,8 @@ pub struct HkrpgModuleContext<T> {
impl<T> HkrpgModuleContext<T> {
fn new(base: usize) -> Self {
Self {
base,
interceptor: Interceptor::default(),
_base: base,
interceptor: Interceptor::new(),
_module_type: PhantomData,
}
}
+4 -6
View File
@@ -10,10 +10,8 @@ impl HkrpgModule for HkrpgModuleContext<Network> {
unsafe fn init(&mut self) -> Result<(), ilhook::HookError> {
let config = rva_config();
if config.make_initial_url != 0 && config.il2cpp_string_new_len != 0 {
self.interceptor.attach(
self.base.wrapping_add(config.make_initial_url),
Network::on_make_initial_url,
)?;
self.interceptor
.attach(config.make_initial_url, Network::on_make_initial_url)?;
println!("[network::init] network patch enabled")
} else {
println!("[network::init] pattern is outdated! disabling http redirection")
@@ -24,7 +22,7 @@ impl HkrpgModule for HkrpgModuleContext<Network> {
impl Network {
const SDK_URL: &str = "http://127.0.0.1:21000";
// const DISPATCH_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;
@@ -40,7 +38,7 @@ impl Network {
s if (s.contains("bhsr.com") || s.contains("starrails.com"))
&& Self::REDIRECT_DISPATCH =>
{
Self::SDK_URL.to_string()
Self::DISPATCH_URL.to_string()
}
s => {
println!("Leaving request as-is: {s}");
+25 -5
View File
@@ -45,7 +45,8 @@ pub unsafe fn scan_il2cpp_section(pat: &str) -> Option<usize> {
match slice.get(address) {
// jmp sub_xxxxxxx
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)
}
// 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)
&& 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),
@@ -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> {
// let mut slice = unsafe { unity_player_slice() };
// scan_first_match(&mut slice, pat)
// .unwrap()
// .map(|loc| UNITY_PLAYER_BASE.wrapping_add(loc))
// scan_first_match(&mut slice, pat).unwrap().map(|address| {
// let slice = unsafe { unity_player_slice() };
// 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),
// }
// })
// }
+54
View File
@@ -0,0 +1,54 @@
use std::{env, fs, path::PathBuf, process::Command};
fn main() {
match env::var("CARGO_CFG_TARGET_ENV").as_deref() {
Ok("msvc") => {
println!("cargo:rustc-link-arg-bin=launcher=/MANIFEST:EMBED");
println!("cargo:rustc-link-arg-bin=launcher=/MANIFESTUAC:level='requireAdministrator'");
}
Ok("gnu") => {
let Some(out_dir) = env::var_os("OUT_DIR") else {
return;
};
let out_dir = PathBuf::from(out_dir);
let manifest = out_dir.join("launcher.manifest");
let resource = out_dir.join("launcher.rc");
let object = out_dir.join("launcher-manifest.o");
if fs::write(&manifest, MANIFEST).is_err() || fs::write(&resource, RESOURCE).is_err() {
return;
}
let Ok(status) = Command::new("x86_64-w64-mingw32-windres")
.current_dir(&out_dir)
.args(["launcher.rc", "-O", "coff", "-o"])
.arg(&object)
.status()
else {
return;
};
if !status.success() {
return;
}
println!("cargo:rustc-link-arg-bin=launcher={}", object.display());
}
_ => {}
}
}
const RESOURCE: &str = "1 24 \"launcher.manifest\"\n";
const MANIFEST: &str = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>
"#;
+45 -3
View File
@@ -4,16 +4,17 @@ use std::ffi::CString;
use std::ptr::null_mut;
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::LibraryLoader::{GetModuleHandleA, GetProcAddress};
use windows::Win32::System::Memory::{
MEM_COMMIT, MEM_RELEASE, MEM_RESERVE, PAGE_READWRITE, VirtualAllocEx, VirtualFreeEx,
};
use windows::Win32::System::Threading::{
CREATE_SUSPENDED, CreateProcessA, CreateRemoteThread, PROCESS_INFORMATION, ResumeThread,
STARTUPINFOA, WaitForSingleObject,
CREATE_SUSPENDED, CreateProcessA, CreateRemoteThread, OpenProcessToken, PROCESS_INFORMATION,
ResumeThread, STARTUPINFOA, WaitForSingleObject,
};
use windows::core::{PSTR, s};
use windows::core::{Error, PSTR, s};
fn inject_standard(h_target: HANDLE, dll_path: &str) -> bool {
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() {
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 dll_path = current_dir.join("hkrpg.dll");
if !dll_path.is_file() {
-12
View File
@@ -1,12 +0,0 @@
[package]
name = "mhypbase"
edition = "2024"
version.workspace = true
[lib]
name = "mhypbase"
crate-type = ["cdylib"]
[dependencies]
windows.workspace = true
hkrpg.workspace = true
-7
View File
@@ -1,7 +0,0 @@
fn main() {
println!(
"cargo:rustc-link-search=native={}",
std::env::var("OUT_DIR").unwrap()
);
println!("cargo:rustc-link-arg=/DEF:mhypbase/mhypbase.def");
}
-3
View File
@@ -1,3 +0,0 @@
LIBRARY MHYPBASE
EXPORTS
Initialize @1
-27
View File
@@ -1,27 +0,0 @@
use windows::Win32::{
Foundation::HINSTANCE,
System::{
SystemServices::DLL_PROCESS_ATTACH,
Threading::{GetCurrentThread, TerminateThread},
},
};
#[unsafe(no_mangle)]
#[allow(non_snake_case, unused_variables)]
extern "cdecl" fn Initialize() -> bool {
std::thread::sleep(std::time::Duration::from_secs(2));
unsafe {
TerminateThread(GetCurrentThread(), 0).unwrap();
}
false
}
#[allow(non_snake_case)]
#[unsafe(no_mangle)]
unsafe extern "system" fn DllMain(_: HINSTANCE, call_reason: u32, _: *mut ()) {
if call_reason == DLL_PROCESS_ATTACH {
std::thread::spawn(hkrpg::main);
}
}