Initial Commit

This commit is contained in:
Tenshi committed 2026-10-31 23:35:41 +01:00
commit 737780597b
51 files changed
+8675

No files matched your search

+37
View File
@@ -0,0 +1,37 @@
# ACE patch (Windows, clients ≤ 8.9)
Builds a patched `BH3.exe` from a capture of the clean client's
initialized state. Only `BH3.exe` is written, without a backup. 9.0+
clients use HoYoKProtect instead.
## Discovery (`ace/discovery.rs`)
Read-only; all checks must pass:
| File | Required layout |
| --- | --- |
| `BH3.exe` | 7 sections all named `.ace*`, 4 KiB section alignment, no TLS directory, entry calls the `BH3Base.dll` ordinal-1 import |
| `BH3_Data/sharedassets0.assets` | `ChannelConfig` resolves to a known region |
| `UnityPlayer.dll` | exports `UnityMain`; three writable decoder globals found via a `.text` code pattern |
| `BH3_Data/Native/UserAssembly.dll` | exports `il2cpp_get_api_table`; executable `.tvm0` section with a virtual tail larger than its raw size |
## Capture (`ace/capture.rs`, `ace/process.rs`)
After confirmation, launches the client (Windows directly; Linux via
`umu-run` + Proton) and reads its memory read-only — no injection or
protection tampering. Once the state is stable for 2 s (120 s timeout),
it captures the `.tvm0` section, the three decoder states and the
committed private executable allocations.
## Normalize and rebuild (`ace/runtime.rs`, `ace/generate.rs`)
`runtime.rs` checks the captured `.tvm0` against the original after PE
relocations, resolves `FF 25` jump slots against the captured
allocations, and emits a position-independent blob and two thunk tables.
`generate.rs` appends `.bh3code` (the `bootstrap.bin` stub, patched with
the discovered RVAs/sizes per `bootstrap.json`) and `.bh3imp` (imports,
decoder states and the runtime blob), then repoints the entry point.
A `HI3AUTO1` receipt with SHA-256 hashes of the patched image,
`UnityPlayer.dll` and `UserAssembly.dll` is appended; a repeat run that
validates it is a no-op. Written via `util::replace::verified`.
+56
View File
@@ -0,0 +1,56 @@
# Architecture
`format/` holds reusable binary-format parsing; `patch/` holds the patch
logic. Patch modules depend on `format/`, `android/` and `util/`, never on
each other.
## format/
All reads are bounds-checked; malformed input returns an error instead of
panicking.
- **bytes** — little/big-endian integer reads and writes, slicing,
power-of-two alignment.
- **pe** — PE32+ (x86-64) reader. Validates headers and section ranges;
provides section lookup, RVA→offset mapping, data directories, export
lookup, and a stricter image-bound check used by ACE and HoYoKProtect.
Patches add their own layout checks on top.
- **elf** — ARM64 shared-object reader. Validates headers and
section/segment ranges; provides sections, segments, `.dynsym` symbols,
RELA entries, and address→file-offset mapping through `PT_LOAD`.
- **dex** — field access, checksum recompute (SHA-1 + Adler-32), string
and `const-string` lookup, and `append_string`, which inserts a
`string_data_item` before the annotations section and relocates every
later offset, map entry and header field.
- **unity** — UnityFS v6 bundles and serialized-file `TextAsset`s.
`rebuild` re-encodes only changed blocks and re-verifies the result.
`replace_script` and `Bundle::replace_node` resize a TextAsset or
bundle node, rewriting object offsets, the header size and the node
directory. Blocks may be uncompressed or LZ4.
## patch/
`region` maps the Windows client's `ChannelConfig`
(`ChannelName`/`BundleIdentifier`) to one of six PC regions
(CN/GLB/JP/KR/TW/SEA) with its SDK key and environment id.
Single-file Windows writes (RSA, ACE, HoYoKProtect) use
`util::replace::verified`: write a sibling temporary file, flush, read it
back, re-check the target for concurrent changes, persist, re-verify. The
dispatch patch replaces its three files together and rolls back on
failure.
## android/
`toolchain` finds Build-Tools and Java 17+, or downloads them (SHA-256
verified). `sign::signed_patch` runs a patch's repack, which drops the old
signature entries, into a temporary APK, then zip-aligns it
(`-P 16 -f 4`), signs and verifies it with `apksigner`, and writes
`<name>_patched.apk`.
## cli/
`interactive` is the terminal menu (no arguments). `args` parses
`<command> --flag value` (or `--flag=value`) and validates the whole
command line before running the patch, then reports via exit code and
optional JSON.
+70
View File
@@ -0,0 +1,70 @@
# Dispatch & SDK patch
Rewrites client URLs to a chosen HTTP or HTTPS server. The server is a
host or IP with an optional port, without scheme or path.
## URL rules (`dispatch/mod.rs`)
- `dispatch` replaces the `"DispatchUrls": [...]` list with
`"<origin>/query_dispatch"` entries inside its original byte range,
space-padding the rest.
- `sdk` rewrites the URLs of the production `bh3_*` records in
`server_pc` (`env` `0` for `bh3_cn`, else `2`), padding each record to
its original length.
- `android_urls` rewrites `http(s)`/`ws(s)` URLs and bare hosts on the
official domains (`bh3.com`, `honkaiimpact3.com`, `mihoyo.com`,
`hoyoverse.com`, plus one known IP). Shorter replacements keep the
original length with an `x…@` userinfo prefix; `ws`/`wss` follow the
chosen scheme.
If a replacement is longer than the original, `dispatch_grow`, `sdk_grow`
or `android_urls_grow` writes it at full length and the asset is
enlarged: `unity::replace_script` rewrites the serialized object table and
header size, and `Bundle::replace_node` re-chunks a UnityFS node and
fixes its directory. The tool then prints a warning that assets were
enlarged.
## Windows (`dispatch/windows.rs`)
Writes three files together and rolls back on failure:
| File | Content patched |
| --- | --- |
| `BH3_Data/sharedassets0.assets` | `ChannelConfig` dispatch URLs; also identifies the region |
| `BH3_Data/resources.assets` | `server_pc` SDK URLs |
| `BH3_Data/StreamingAssets/MiHoYoSDKRes/PC/mihoyo_sdk_res` | the `server_pc` copy inside the SDK bundle |
Both `server_pc` copies must be byte-equal before patching.
## Android (`dispatch/android.rs`)
Patches serialized files, UnityFS bundle nodes and plain text among the
candidate `assets/` entries (`assets/bin/data/*` and known text
extensions, except `package_config.json`, `share_config.json`, `.wmv`
and `.bak`). `.splitN` parts are joined for patching and re-split at the
original chunk size; enlarging can add parts. DEX, native libraries and
`.wmv` are copied unchanged, except for the CN passport URL below. At
least one `ChannelConfig` and one SDK/resource URL are required, otherwise
no APK is written. The result is repacked and signed by `android::sign`.
## CN passport URL (`dispatch/cn_android.rs`)
The CN passport login URL is a plaintext string in the DEX that declares
`…account.sdk.utils.RSAUtils`; only that DEX is edited. The server must
use **HTTP** — HTTPS is rejected.
The stock DEX has exactly one loopback string, `http://127.0.0.1` (an SDK
default), between known neighbors in the sorted `string_ids` table. The
patch:
1. Appends a `string_data_item` for the chosen origin
(`format::dex::append_string`) and points the loopback string id at
it.
2. Points the `const-string` that loaded the official login URL
(`https://passport-api.mihoyo.com`) at that id, so login goes to the
server.
3. Moves the original loopback `const-string` to the neighboring id to
keep `string_ids` sorted.
The origin must sort between those neighbors; other addresses are
rejected. Checksums are recomputed.
+28
View File
@@ -0,0 +1,28 @@
# HoYoKProtect patch (Windows, clients 9.0+)
Makes the `IsLoadMHYBase` predicate in `BH3_Data/Native/UserAssembly.dll`
return false, turning the client-side MHYP loader off. Only that file is
written; the region is confirmed from `ChannelConfig` first.
## Discovery (`hoyokprotect/discovery.rs`)
1. In `.text`, match the encoded `IsLoadMHYBase` string construction by
byte pattern (not a fixed RVA); exactly one match.
2. The exception directory gives the containing function's bounds. It
must start with the known prologue, end with the known boolean-return
epilogue, and contain the match.
3. Exactly two relative `CALL`s must target the function, followed by the
known module-check and initialization byte sequences.
`il2cpp_get_api_table` must be exported. Unknown or ambiguous results
abort.
## Edit
| Location | Original | Replacement | Effect |
| --- | --- | --- | --- |
| First 3 bytes of the function | `55 41 57` | `31 C0 C3` | `xor eax, eax; ret` |
Discovery re-runs on the edited image to confirm the disabled form, then
the file is written via `util::replace::verified`. An already-disabled
function is left unchanged.
+81
View File
@@ -0,0 +1,81 @@
# Android protection removal
One pipeline removes AnoSDK (overseas) or Mobile ACE/TSS (CN); the
variants differ only in the constants in `protection/profile.rs`, and
`--variant auto` picks one from the libraries present. It restores four
protected ARM64 libraries, neutralizes the SDK startup and native API
calls, drops the protection libraries, and signs a new APK.
## Variant profiles
| Field | AnoSDK | Mobile ACE/TSS |
| --- | --- | --- |
| Regions | SEA, GLB, JP, KR, TW | CN |
| Decoder library | `libanort.so` | `libtprt.so` |
| Guard library | `libanogs.so` | `libtersafe2.so` |
| Key record XOR | `0xDF` | `0x49` |
| Decoder symbol | `g_acf_array` | `g_tprt_pfn_array` |
| `Application` class | `com.ano.gshell.AnoApplication` | `com.ace.gshell.AceApplication` |
| SDK class | `com.gamesafe.ano.AnoSdk` | `com.tencent.tp.TssSdk` |
| Native APIs | 6 `AnoSDK*` | 5 `tp2_*` |
## Identification (`protection/mod.rs`)
Checks `ChannelConfig` (`ChannelName`/`BundleIdentifier`) against the
variant's regions, confirms both protection libraries are ARM64 ELFs, and
rejects APKs that also contain the other variant's libraries. ARM64 is
the only supported ABI.
## Restore (`protection/loader.rs`)
- `permutation` follows the decoder symbol through its routine and GOT
reference to the eight-page permutation (a digit string permuting
`0..8`).
- Each 48-byte protected-section descriptor is matched to an ELF section
by name, RVA, file offset and size. The Base64 key record at descriptor
table `+0x1A4` is XORed with the variant byte: magic `0x20240829`, key
id 1 drives restoration, and the mode at `+0x184` (1 or 2) selects page
treatment. Each restored section must match its recorded CRC-32.
- Large sections: AES-128-CBC over `0x4000` bytes in `0x800`-byte
chunks from `0x2000 - (RVA & 0xFFF)` (key: the value's repeated
lowercase hex; IV `0x02..0x11`, reset per chunk). Mode 1 XORs and
shuffles `0x4000` fragments across eight `0x10000` strides; mode 2
XORs the remainder.
- Small sections: whole-section XOR.
- `BBMG311DIS12IMP` gives the protected import count (`+16`), `.rela.plt`
RVA (`+24`), entry count (`+28`) and excluded GOT slots (`+32/+36`);
selected import `r_info` values are rotated back into order.
`BBMG311ENC12IAI` gives the constructor array RVA (`+16`) and count
(`+20`). The wrapper `DT_INIT`, the decoder library's `DT_NEEDED` entry
and the APS2/RELA wrapper relocations are cleared, and the
`fread`/`fwrite`/`fclose` GOT imports are restored. The removed
`DT_INIT` RVA is passed to the API helper.
## Neutralize (`protection/native.rs`)
In restored `libil2cpp.so`, finds the SDK API names and the guard library
name in `.rodata`, traces ADRP/ADD references to them, and redirects each
resolver `BL` to a 16-byte helper written over the removed `DT_INIT`
initializer. The helper returns a pointer to a function that returns
zero; other P/Invoke resolutions are untouched. Aborts if the initializer
is still active, an API is missing, or the call graph is ambiguous.
## DEX edits (`protection/dex.rs`)
Three methods are edited after each call is validated:
| Method | Edit |
| --- | --- |
| `Application.<clinit>` | NOP both native library loads (20 bytes) |
| `Application.attachBaseContext` | Replace the invoke + move-result after the superclass call with return/NOPs (8 bytes) |
| SDK class `<clinit>` | NOP the guard library load (`const-string` 10 bytes, or `const-string/jumbo` 12 bytes); keep the return |
All 8 `attachBaseContext` bytes are rewritten because leftover operand
words after the `return` would still fail DEX verification. Checksums are
recomputed.
## Output
The decoder and guard libraries are dropped; the restored libraries and
edited DEX files are repacked and signed by `android::sign`. Any
mismatched marker, CRC, method or resolver layout aborts the patch.
+51
View File
@@ -0,0 +1,51 @@
# Passport RSA patch
Replaces the passport login RSA public key so credentials are encrypted
for the server's `Passport:RsaPrivateKey`. Input: a single-line Base64 DER
`SubjectPublicKeyInfo` body (no PEM markers), 2048-bit, exponent 65537;
empty uses the built-in PemukulPaku key.
## Windows (`rsa/windows.rs`)
Target: `BH3_Data/Native/UserAssembly.dll` (x64 PE), after confirming the
region from `ChannelConfig`.
1. Find the shared XML RSA routine (prefix `CRYPTO`) in the executable
`il2cpp` section and the nearby account/password encryption `CALL`
pair that targets it. A missing or ambiguous match aborts before any
write.
2. Base64-encode the key's modulus (344 characters).
3. Append a `.hi3rsa` code section: an `HI3RSA01` header (the two
original call RVAs and the routine RVA), the wrapper, and the modulus.
Redirect both login `CALL`s into the wrapper; the routine and its
other callers are untouched.
4. Update the section count, code/image sizes and certificate directory
offset (the certificate is kept; its signature no longer matches),
re-verify the image, and write via `util::replace::verified`.
At runtime the wrapper receives the managed XML key in `RCX`. If the key
is non-null, 505 UTF-16 chars long, and has the `<Modulus>` tags at the
expected offsets, it writes the new modulus at byte offset `0xE2`; in
every case it then calls the original routine.
A repeat run validates the existing `.hi3rsa` section and redirected
calls and replaces only the modulus. A single known 80-byte `jk98…`
overlay before the certificate is preserved; other overlays are rejected.
## Android overseas (`rsa/android.rs`)
`…oversea.sdk.internal.shared.crypto.RSAUtils.getRSA_PUBLIC_KEY`
references three environment keys (398-byte, newline-wrapped 2048-bit) via
`const-string`. All three are replaced in place with the chosen key;
wrapping at 63/64/65 characters keeps the strings distinct and in sort
order. String order is re-validated and checksums are recomputed.
## Android CN (`rsa/cn_android.rs`)
`…account.sdk.utils.RSAUtils` holds a 1024-bit key string. A 398-byte
wrapped 2048-bit replacement is appended (`format::dex::append_string`),
the key and its two neighbor string ids are reordered to keep sort order,
and the field, both `const-string` references and both static values are
remapped. Checksums are recomputed; unknown layouts are rejected.
An APK containing both CN and overseas SDKs is rejected.