initial commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
.vscode
|
||||
332
Cargo.lock
generated
Normal file
332
Cargo.lock
generated
Normal file
@@ -0,0 +1,332 @@
|
||||
# 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.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
|
||||
|
||||
[[package]]
|
||||
name = "ext"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"hkrpg",
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hkrpg"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ilhook",
|
||||
"patternscan",
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[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 = "launcher"
|
||||
version = "0.1.0"
|
||||
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.171"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "mhypbase"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"hkrpg",
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "patternscan"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cbf9ac94ae7c3d7f743ec57e0b6b05077631c1a90c6cea9b162a69efa6d6bbde"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.94"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.54.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.54.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65"
|
||||
dependencies = [
|
||||
"windows-result",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8"
|
||||
dependencies = [
|
||||
"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"
|
||||
27
Cargo.toml
Normal file
27
Cargo.toml
Normal file
@@ -0,0 +1,27 @@
|
||||
[workspace]
|
||||
members = ["hkrpg", "launcher", "mhypbase", "ext"]
|
||||
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
version = "0.1.0"
|
||||
|
||||
[workspace.dependencies]
|
||||
windows = { version = "0.54.0", features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_System_SystemServices",
|
||||
"Win32_System_LibraryLoader",
|
||||
"Win32_System_Console",
|
||||
"Win32_System_Threading",
|
||||
"Win32_System_Memory",
|
||||
"Win32_System_Diagnostics",
|
||||
"Win32_Security",
|
||||
"Win32_System_Diagnostics_Debug",
|
||||
"Win32_System_ProcessStatus",
|
||||
"Win32_System_SystemInformation",
|
||||
] }
|
||||
ilhook = "2.1.1"
|
||||
patternscan = "1.2.0"
|
||||
|
||||
# Local crates
|
||||
hkrpg = { path = "hkrpg/" }
|
||||
10
README.md
Normal file
10
README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
redirect HTTP requests & remove censorship!
|
||||
|
||||
currently, this has only been tested on CNBETAWin3.1.53 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.
|
||||
|
||||
Sources:
|
||||
|
||||
- [trigger-patch by xeondev](https://git.xeondev.com/ObolSquad/trigger-patch)
|
||||
- [hk4e-patch-universal by oureveryday](https://github.com/oureveryday/hk4e-patch-universal)
|
||||
12
ext/Cargo.toml
Normal file
12
ext/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "ext"
|
||||
edition = "2024"
|
||||
version.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "hkrpg"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
windows.workspace = true
|
||||
hkrpg.workspace = true
|
||||
9
ext/src/lib.rs
Normal file
9
ext/src/lib.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use windows::Win32::{Foundation::HINSTANCE, System::SystemServices::DLL_PROCESS_ATTACH};
|
||||
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
9
hkrpg/Cargo.toml
Normal file
9
hkrpg/Cargo.toml
Normal 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
117
hkrpg/src/addr.rs
Normal 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);
|
||||
}
|
||||
43
hkrpg/src/il2cpp_string.rs
Normal file
43
hkrpg/src/il2cpp_string.rs
Normal 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
41
hkrpg/src/interceptor.rs
Normal 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
41
hkrpg/src/lib.rs
Normal 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));
|
||||
}
|
||||
}
|
||||
43
hkrpg/src/modules/censorship_patch.rs
Normal file
43
hkrpg/src/modules/censorship_patch.rs
Normal 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
|
||||
}
|
||||
}
|
||||
30
hkrpg/src/modules/hk_check.rs
Normal file
30
hkrpg/src/modules/hk_check.rs
Normal 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
51
hkrpg/src/modules/mod.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
60
hkrpg/src/modules/network.rs
Normal file
60
hkrpg/src/modules/network.rs
Normal 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
86
hkrpg/src/util.rs
Normal 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))
|
||||
}
|
||||
7
launcher/Cargo.toml
Normal file
7
launcher/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "launcher"
|
||||
edition = "2024"
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
windows.workspace = true
|
||||
106
launcher/src/main.rs
Normal file
106
launcher/src/main.rs
Normal file
@@ -0,0 +1,106 @@
|
||||
// Source: https://git.xeondev.com/NewEriduPubSec/JaneDoe-Patch/src/branch/master/launcher/src/main.rs
|
||||
|
||||
use std::ffi::CString;
|
||||
|
||||
use std::ptr::null_mut;
|
||||
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::{
|
||||
MEM_COMMIT, MEM_RELEASE, MEM_RESERVE, PAGE_READWRITE, VirtualAllocEx, VirtualFreeEx,
|
||||
};
|
||||
use windows::Win32::System::Threading::{
|
||||
CREATE_SUSPENDED, CreateProcessA, CreateRemoteThread, PROCESS_INFORMATION, ResumeThread,
|
||||
STARTUPINFOA, WaitForSingleObject,
|
||||
};
|
||||
use windows::core::{PSTR, s};
|
||||
|
||||
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!(
|
||||
"Failed allocating memory in the target process. GetLastError(): {:?}",
|
||||
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::<
|
||||
unsafe extern "system" fn() -> isize,
|
||||
unsafe extern "system" fn(*mut std::ffi::c_void) -> u32,
|
||||
>(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("hkrpg.dll");
|
||||
if !dll_path.is_file() {
|
||||
println!("hkrpg.dll not found");
|
||||
return;
|
||||
}
|
||||
|
||||
let mut proc_info = PROCESS_INFORMATION::default();
|
||||
let startup_info = STARTUPINFOA::default();
|
||||
|
||||
unsafe {
|
||||
CreateProcessA(
|
||||
s!("StarRail.exe"),
|
||||
PSTR(null_mut()),
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
CREATE_SUSPENDED,
|
||||
None,
|
||||
None,
|
||||
&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();
|
||||
}
|
||||
}
|
||||
12
mhypbase/Cargo.toml
Normal file
12
mhypbase/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "mhypbase"
|
||||
edition = "2024"
|
||||
version.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "mhypbase"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
windows.workspace = true
|
||||
hkrpg.workspace = true
|
||||
7
mhypbase/build.rs
Normal file
7
mhypbase/build.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
fn main() {
|
||||
println!(
|
||||
"cargo:rustc-link-search=native={}",
|
||||
std::env::var("OUT_DIR").unwrap()
|
||||
);
|
||||
println!("cargo:rustc-link-arg=/DEF:mhypbase/mhypbase.def");
|
||||
}
|
||||
3
mhypbase/mhypbase.def
Normal file
3
mhypbase/mhypbase.def
Normal file
@@ -0,0 +1,3 @@
|
||||
LIBRARY MHYPBASE
|
||||
EXPORTS
|
||||
Initialize @1
|
||||
27
mhypbase/src/lib.rs
Normal file
27
mhypbase/src/lib.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user