189 Commits

Author SHA1 Message Date
cb3db372df v3.1.1 2024-02-06 00:05:07 +02:00
2fee742f75 SR 2.0.0: bump game version 2024-02-05 16:28:28 +02:00
b36d217284 SR 2.0.0: update checksums 2024-02-05 16:27:29 +02:00
8520356083 General repo maintenance 2024-02-04 10:18:22 +02:00
9bd1379244 Document that the Steam version of 3rd works 2024-02-04 10:15:51 +02:00
7d7967f477 Marked 3rd cn v7.3.0 as verified 2024-02-02 12:13:03 +02:00
8550abdf9f Marked HI3 glb/sea/tw/kr/jp 7.2.0 as verified 2024-01-18 20:11:10 +02:00
912bc2e55f Fixed documentation 2024-01-08 16:18:51 +00:00
f0da1b8456 Documented experimental patching method 2024-01-08 18:17:13 +02:00
af0c685fb3 v3.1.0 2024-01-08 17:58:56 +02:00
35cd117ffc Refactored dynamically linking ntdll 2024-01-07 20:51:55 +02:00
9f011ff103 Marked buf const in utils_write_protected_memory 2024-01-07 19:02:02 +02:00
ecbab96427 Implemented experimental patching method for SR 2024-01-07 19:01:03 +02:00
2da21065a6 Refactored protected memory writes 2024-01-07 18:34:03 +02:00
f8c4c5ad82 Replaced dummy variable references with NULL in inject.c
Apparently the crashes were an artifact of a different thing, and don't actually happen under normal conditions
2023-12-29 14:14:05 +02:00
6b5e303df6 Updated documentation 2023-12-28 14:35:29 +02:00
84e2c172a3 Marked msg_err functions noreturn 2023-12-27 14:11:02 +02:00
981a78ae53 Marked SR v1.6.0 as verified 2023-12-27 11:33:21 +02:00
55324bb5a8 v3.0.12 2023-12-27 00:40:43 +02:00
211f40ae53 SR 1.6.0: mark unverified 2023-12-27 00:37:12 +02:00
deddf93d3f SR 1.6.0: bump game version 2023-12-25 16:18:10 +02:00
c37d0e92f4 SR 1.6.0: updated checksums, patch address 2023-12-25 16:16:08 +02:00
fa3a3aa9cc Marked 3rd cn v7.2.0 as verified 2023-12-23 18:55:30 +02:00
186471508f Documented Wine 8.21+ fixing video playback 2023-12-23 18:54:41 +02:00
1f0a6bc016 Mark SR v1.5.0 as verified 2023-12-17 15:39:05 +02:00
da08b565f8 Mark 3rd glb/sea/tw/kr/jp v7.1.0 as verified 2023-12-08 00:10:05 +02:00
ead6489e63 Minor file structure reorganization 2023-11-16 22:58:31 +02:00
19d6139e13 v3.0.11 2023-11-15 18:50:56 +02:00
0047d7b64f Marked SR 1.5.0 as concerning 2023-11-13 23:08:53 +02:00
264cdc2528 Documentation update 2023-11-13 22:18:26 +02:00
ab4615dfe9 SR 1.5.0: updated checksums, patch address 2023-11-13 22:13:43 +02:00
cf34ac6e01 Mark 3rd cn v7.1.0 verified 2023-11-09 16:23:48 +02:00
6c7bbabced v3.0.10 2023-10-27 08:26:18 +03:00
81ddaeeda7 Marked 3rd 7.0.0 as verified 2023-10-27 08:25:58 +03:00
b9cae22212 Removed section name workaround for 3rd 2023-10-27 08:23:50 +03:00
8201e29e26 Remove misleading remark in readme 2023-10-11 17:55:48 +00:00
71144b4cd1 Mark HSR v1.4.0 as verified 2023-10-11 17:54:44 +00:00
5af8d2463a Set v3.0.9 in metadata.json 2023-10-10 22:02:36 +00:00
ea1d2f91f2 v3.0.9 2023-10-11 00:14:36 +03:00
f0ed718cf3 Document HSR 1.4.0 support 2023-10-09 19:27:24 +03:00
bc09349b8e Update checksums for HSR 1.4.0 2023-10-09 19:22:07 +03:00
66e51c6d9a Mark HI3 cn v7.0.0 verified 2023-10-01 11:04:01 +03:00
bc7b24f4d9 v3.0.8 2023-09-26 12:06:52 +03:00
9b55d4f160 Document HI3 forward compatibility 2023-09-26 12:06:24 +03:00
57b2423a8a Implement a better workaround for different HI3 editions having different section names 2023-09-26 12:05:53 +03:00
e96bf23a30 Document unexpected lack of change in HI3 v6.9.0 2023-09-15 23:18:02 +03:00
9296e00edd Document mfplat fix being unavailable 2023-09-11 19:30:52 +00:00
d22ded3d27 v3.0.7 2023-08-30 01:06:26 +03:00
15c403b0d1 Improve invalid checksum error message 2023-08-30 01:05:53 +03:00
1cc16ecbd9 Document HSR 1.3.0 support 2023-08-28 18:21:13 +03:00
ffe75e9de7 Update checksums to HSR 1.3.0 2023-08-28 18:19:36 +03:00
243e39d04b Optimized memory allocation in core 2023-08-26 21:24:56 +03:00
daa5585527 Update game version list in the readme 2023-08-17 23:11:46 +03:00
8bf5aac0ec v3.0.6 2023-08-17 22:57:32 +03:00
4c7dd302d5 Merged workaround for 3rd 2023-08-17 22:55:26 +03:00
63eed9d796 Handle missing txs section 2023-08-17 22:53:12 +03:00
671f9ba246 Add temporary workaround for 3rd 6.9.0 2023-08-17 22:51:43 +03:00
868b4e27be Reference 6.9.0 issue in readme 2023-08-17 18:23:54 +00:00
56eefd83d7 Mark 3rd cn 6.9.0 as broken 2023-08-17 19:10:52 +03:00
3b2d33ad24 v3.0.5 2023-08-12 17:22:34 +03:00
ba6a054ec3 Show executable path in failed to start game process messagebox 2023-08-12 17:20:58 +03:00
7693e63619 Update core.md 2023-08-12 17:16:05 +03:00
5d5d2117ec 3.0.4 2023-08-12 02:32:20 +03:00
ca65bbd70c Reformat invalid checksum messagebox 2023-08-12 02:29:27 +03:00
c17cf00409 Move LoadLibrary call into core 2023-08-12 02:16:19 +03:00
c80635fc71 Adapted utils_create_dir_recursively to general project style 2023-08-10 01:15:53 +03:00
e9d2130105 Minor game detect refactoring 2023-08-10 01:00:24 +03:00
0bfab4f682 v3.0.3 2023-08-06 22:09:57 +03:00
e0fcca3701 Fix block_analytics.sh 2023-08-06 22:09:44 +03:00
3b7cda6c5f Mention 'chmod +x' in the readme 2023-08-06 19:32:29 +03:00
e066466339 v3.0.2 2023-08-06 19:27:47 +03:00
612c2e74e6 Added block_analytics.sh script 2023-08-06 19:26:02 +03:00
4060fb5d4e Added handling for more error conditions 2023-08-06 19:01:44 +03:00
54978e367c Fixed multiple error messageboxes showing invalid characters 2023-08-06 18:55:40 +03:00
e0d89875a0 Updated core.md 2023-08-05 18:37:19 +03:00
89c6347315 v3.0.1 2023-08-05 18:32:56 +03:00
de15c00f2a Fix 3rd crash 2023-08-05 18:32:29 +03:00
694c734e67 v3.0.0 2023-08-05 17:38:30 +03:00
0641006998 Update readme 2023-08-05 12:27:26 +03:00
79cf7d20cc Finish integrating TX 2023-08-05 12:15:08 +03:00
505d4b12dd Specify full relative assembly path 2023-08-05 09:28:36 +03:00
bb8d41c06d Properly append version to the messagebox title 2023-08-05 09:22:15 +03:00
b8aa6f968b Load table dynamically from FS 2023-08-04 23:22:26 +03:00
9a3d623883 Get rid of tables in resources 2023-08-04 23:01:53 +03:00
6b9f9b6d93 Get rid of game_id 2023-08-04 23:00:42 +03:00
400729a3dc Implement table saving functionality 2023-08-04 22:55:10 +03:00
7eac309372 Begin integrating TX 2023-08-04 22:17:31 +03:00
592ce62e6b Implement utils_file_exists, use wide strings for paths 2023-08-04 21:09:16 +03:00
4911f8d903 Implement restart flag 2023-08-04 15:35:29 +03:00
970561afb9 Change pe_find_section interface again, add error handling 2023-08-04 14:28:30 +03:00
dcb482ab8e Change pe_find_section interface 2023-08-04 00:28:53 +03:00
5e2b015cc0 Optimize game_data struct 2023-08-04 00:19:02 +03:00
7beab899a6 Update SERVERS.txt to include new HI3 regions 2023-08-03 17:22:07 +03:00
ac67488255 v2.0.1 2023-08-03 09:08:29 +03:00
1375549216 Add support for 3rd 3.8.0 glb/sea/tw/kr/jp 2023-08-03 09:07:18 +03:00
19056bed0d Update 3rd tables to 3.8.0 2023-08-03 09:04:04 +03:00
8dfe04d005 v2.0.0 2023-08-03 08:35:18 +03:00
6bdb04a925 Document newly supported HI3 regions 2023-08-03 00:09:47 +03:00
9ccde2618b Update metadata.json with new HI3 regions 2023-08-03 00:02:35 +03:00
ce58ec89ef Implement multiregion support for 3rd 2023-08-02 23:32:12 +03:00
5b1ac8533d Add tables for 3rd sea/cn/tw/kr/jp 2023-08-02 23:17:35 +03:00
e554c8e57b Major core rewrite 2023-08-02 18:32:26 +03:00
be445e6db9 Fix _create_driver_file error message 2023-08-02 02:29:18 +03:00
6279bb573d Make _load_module_patched function static 2023-08-02 02:24:39 +03:00
eb38894de5 Refactor ace.c 2023-08-02 01:42:04 +03:00
2612ad2212 Only store the name of the game assembly 2023-08-02 01:39:39 +03:00
b3f64ba6f6 v1.1.13 2023-07-31 15:13:59 +03:00
dd15dc60e3 Remove I_WANT_A_BAN requirement 2023-07-30 00:50:04 +03:00
ac68448cbd Mark HSR as verified 2023-07-30 00:38:31 +03:00
80c817cb6b Renamed tp6.c to core.c 2023-07-28 01:51:04 +03:00
4e614e1d82 v1.1.12 2023-07-19 01:17:44 +03:00
8b9f8e68aa Change HSR version to v1.2.0 2023-07-17 23:57:58 +03:00
99c0c20a3d Add tables for SR v1.2.0 2023-07-17 23:56:37 +03:00
64a25b1607 Update checksums for SR 1.2.0 2023-07-17 23:56:37 +03:00
43e8adaf12 Change SR status to "unverified" from "unsafe" 2023-07-17 23:54:47 +03:00
0004c26d7a Recover the executable memory to it's original state 2023-07-16 17:29:06 +03:00
848ae06792 Write recovery data into the inject allocation 2023-07-16 14:58:18 +03:00
c979c980c1 v1.1.11 2023-07-09 00:03:31 +03:00
3d943b641b Fix additional issue introduced by previous rework 2023-07-09 00:01:48 +03:00
215af6e3db v1.1.10 2023-07-08 23:44:25 +03:00
7aef85851c Fixed subtle core patch bug introduced by previous rework 2023-07-08 23:43:34 +03:00
f7c24f5ab7 v1.1.9 2023-07-08 21:24:17 +03:00
2ebd40b621 Internal rework to avoid a possible edge case 2023-07-08 21:17:28 +03:00
7f43a042e8 v1.1.8 2023-07-08 16:34:12 +03:00
8164694c8f Point user to Jadeite Readme 2023-07-08 15:11:52 +02:00
61e657b2e4 Readme maintenance 2023-07-07 14:14:28 +03:00
7a8087e8a1 Refactor resource gen script 2023-07-07 13:59:28 +03:00
351fe85e2f Move blob file to objects kwarg in target declaration 2023-07-06 00:04:41 +03:00
938b663bd9 Change blob file extension 2023-07-06 00:02:17 +03:00
df1f611199 Publish precompiled tp6.c blob (lots of dirty hacks) 2023-07-05 23:59:27 +03:00
94efee7496 Don't use narrow characters with widestring functions 2023-07-04 01:08:03 +03:00
06be53ad8e Fix an incredibly dumb bitflag error 2023-07-04 01:03:21 +03:00
ce427556a3 More efficient string comparisions 2023-07-03 17:04:32 +03:00
15f56c9e5a v1.1.7 2023-07-03 14:07:38 +03:00
b860834be1 Switch to using wide strings in the injector 2023-07-03 14:04:04 +03:00
8c900f93fc Fix freeing heap on directory check 2023-07-03 11:57:08 +03:00
a7d68776bd Update credits section 2023-07-02 23:48:54 +03:00
6d742b2a15 v1.1.6 2023-07-02 23:29:47 +03:00
cf5d87f7a7 Refuse to launch if the patcher is inside the game directory 2023-07-02 23:21:17 +03:00
181d14e4ce Minor readme styling changes 2023-07-02 20:51:49 +03:00
0067ceb85c Document AAT third-party launchers 2023-07-02 20:50:49 +03:00
33cf0a65e8 Document HI3 v6.7.0 support 2023-06-30 11:55:12 +03:00
d30a2aba9e Update comment in hi3.c 2023-06-29 12:58:57 +03:00
05ed4980c5 v1.1.5 2023-06-29 12:52:46 +03:00
1341e1600c Add tables for HI3 v6.7.0 glb 2023-06-29 12:52:08 +03:00
0e2b60aecb Move envvar defs into a separate header 2023-06-26 12:53:07 +03:00
33f7dd89a9 Optimize assembly payloads 2023-06-26 12:25:44 +03:00
851ebb5b9a Use nonvolatile registers for storage 2023-06-26 12:18:25 +03:00
a0e79dcea0 Major injector refactoring 2023-06-25 12:32:19 +03:00
55fd21feef Move common assembly into a separate file 2023-06-25 02:19:24 +03:00
22a7fb2a60 Use NASM local labels 2023-06-25 01:51:18 +03:00
39147ac049 Show a warning if someone tries to run build.sh 2023-06-25 01:47:32 +03:00
9a1405c828 Use winapi types in pe.c 2023-06-25 01:42:32 +03:00
5473908df4 Show invalid UnityPlayer.dll checksum in hex 2023-06-23 20:00:27 +03:00
6a237fd247 Only write the modified field in the exe header 2023-06-23 18:55:22 +03:00
95dec1b0fd Remove useless includes in hsr.c 2023-06-21 17:47:42 +03:00
661ef826c3 Move magic number into def 2023-06-21 16:24:16 +03:00
6209157cf2 v1.1.4 2023-06-21 15:42:19 +03:00
f26bcbd0fc HOTFIX: fix module unloading with SRFIX_DISABLE 2023-06-21 15:41:46 +03:00
14c90f7137 v1.1.3 2023-06-21 14:52:38 +03:00
8f96ec4eec Unpatch WriteTextureStatisticUserData after execution 2023-06-21 14:47:46 +03:00
5421487212 Counter-based unloading logic 2023-06-21 14:25:22 +03:00
326ccd188e v1.1.2 2023-06-11 18:05:58 +03:00
0b0216e41e Pass commandline arguments to the game process
Closes #4
2023-06-11 18:04:24 +03:00
4c0c35ba43 Mark HSR unsafe for third-party launchers 2023-06-10 19:39:57 +03:00
e299d264de Don't include metadata.json in the release archives 2023-06-10 19:37:09 +03:00
cba9b20171 v1.1.1 2023-06-10 19:24:07 +03:00
5640987ead metadata.json for third-party launchers 2023-06-10 19:19:11 +03:00
54a127b848 Minor injector and launcher payload refactoring 2023-06-10 18:28:19 +03:00
3994188b08 Add WAIT_BEFORE_RESUME envvar 2023-06-10 18:23:43 +03:00
7ffce8fe3a Update readme 2023-06-10 13:07:47 +03:00
5143dc0d56 Minor readme styling changes 2023-06-09 21:10:44 +03:00
0afdf60cc6 Add HSR logging servers to SERVERS.txt 2023-06-08 23:01:07 +03:00
ed983d2b25 v1.1.0 2023-06-08 22:35:43 +03:00
f8954d0857 Implement configuration variables 2023-06-08 22:33:37 +03:00
8662c84a0a New messagebox system 2023-06-08 21:44:42 +03:00
38dbf82f7f Implement shared resources fix for HSR 2023-06-08 20:13:21 +03:00
2584898be6 Add resources for HSR 2023-06-08 19:38:36 +03:00
006ff30914 Initial HSR support 2023-06-08 18:36:22 +03:00
88568b374d Move UnityPlayer loading into the game payload DLL 2023-06-08 15:27:51 +03:00
30b79c1cb3 Update .gitignore 2023-06-07 23:55:14 +03:00
e29b27bfd1 v1.0.1 2023-06-07 21:00:17 +03:00
bcbc679a21 Optimized build 2023-06-07 20:57:56 +03:00
435f1b0355 Allow launching the injector from a different directory 2023-06-07 20:09:02 +03:00
f965bb79a3 Merge hi3.h into game.h 2023-06-06 21:14:21 +03:00
a79ebdc0c8 Remove redundant "assembly_name_lwr" field from "struct game_data" 2023-06-06 20:52:48 +03:00
0dea9e7625 Show a messagebox if the game failed to start 2023-06-06 20:36:00 +03:00
c1cfed6216 Move envvar names into injshared.h 2023-06-06 20:23:15 +03:00
ab0668d3a9 Fix typo in readme 2023-06-06 20:21:06 +03:00
56 changed files with 1386 additions and 684 deletions

3
.gitignore vendored
View File

@ -3,8 +3,9 @@
.directory
# File withheld to make abuse more difficult
game_payload/src/tp6.c
game_payload/src/core.c
build
out
jadeite.zip
v*.zip

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2023 mkrsym1 <mkrsym1@gmail.com>
Copyright (c) 2023-2024 mkrsym1 <mkrsym1@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,49 +1,75 @@
# PROOF OF CONCEPT. DO NOT USE IF YOU DON'T KNOW WHAT YOU'RE DOING
# Jadeite Autopatcher
### Current game support:
- **3rd**: glb/sea/tw/kr/jp **v7.2.0+**, cn **v7.3.0+**
- **SR**: os/cn **v2.0.0**\*
### Games and regions
This project is in the proof-of-concept stage. Currently, only **3rd glb v6.6.0** is supported. It may be possilbe to completely remove the region and version-specific data in the future. Refer to the source code in `game_payload/src` for details.
You can expect newer versions to work immediately after release with the same jadeite binary if the version is specified with a + above.
### Information
The anticheat the games use is fundamentally incompatible with Wine in multiple ways. This tool launches the game without it (`inject/launcher_payload`) and imitates it's behaviour (`game_payload`).
**Note:** you can test the experimental version-independent patching method for SR. See [#37](https://codeberg.org/mkrsym1/jadeite/issues/37).
Does not work on Windows.
**Note:** the Steam version of 3rd was reported to work, however I neither tested it nor intend to support it, so it might break at any time. Use at your own risk.
### Usage
**Refer to [Third-party launchers](#third-party-launchers) (will be written later)** for convenient usage. If you don't want to (or can't) use third-party launchers, continue reading the section below.
## Information
The anticheat the games use is fundamentally incompatible with Wine in multiple ways. This tool launches the game without it (`injector`) and imitates it's behaviour (`game_payload`).
**Wine 8.0+ is recommended**, as lower versions leak "The Wine project" as the device identifier. Not critical, but taking a precaution never hurt anyone. **DXVK is strongly recommended.**
**Using third-party software (such as this tool) with the games violates their Terms of Service**. Therefore, **you may receive a ban**. No bans were ever reported with 3rd, however the legacy patch for SR did cause many. **Use at your own risk and only if you understand all the possible consequences**.
3rd-specific: In some cases, and if you're not using Proton GE, **a fix for Media Foundation may be required to play videos. The Game may crash without it.** You can download it from [here](https://github.com/z0z0z/mf-install). You might need to [limit the number of cores available to the game](https://github.com/z0z0z/mf-install/issues/44) if your CPU has more than 8.
**This is not a cheating tool**. Using it with Windows is not possible, and Windows support is not planned or intended in any way. However, as it does not perform any on-disk file modifications, you may reuse the same game install for Windows if you have a dual-boot setup.
## Usage
**Refer to [third-party launchers](#third-party-launchers)** for convenient usage. If you don't want to (or can't) use third-party launchers, continue reading the section below
**Wine 8.0+ is recommended**, as lower versions leak "The Wine project" as the device identifier. Not critical, but taking a precaution never hurt anyone. **DXVK is strongly recommended**
**3rd-specific**:
- **Wine 8.21+ or [Wine-GE-Proton](https://github.com/GloriousEggroll/wine-ge-custom) is required to play videos. The game may show a black screen or crash on lower versions.**
- You might need to [limit the number of cores available to the game](https://github.com/z0z0z/mf-install/issues/44) if your CPU has more than 8.
Manual usage instructions:
- Download the game you want to run
- Download a release from this repository
- Download the latest release from this repository
- Extract the archive (**NOT INTO THE GAME DIRECTORY! THIS IS IMPORTANT!**)
- Block analytics servers in your `hosts` file. You can find the list in SERVERS.txt
- Run `wine jadeite.exe "Z:\\wine\\path\\to\\game.exe"`
- Run `./block_analytics.sh` from the archive to block the games from accessing analytics servers (you might have to do a `chmod +x block_analytics.sh` first). This will require superuser privileges
- Run `wine jadeite.exe 'Z:\wine\path\to\game.exe'`
This tool is capable of starting the games from a different process. This may be useful for spoofing the parent process (SR is known to report it). Use `wine jadeite.exe "Z:\\wine\\path\\to\\game.exe" "Z:\\wine\\path\\to\\launcher.exe"`. `explorer.exe` is used as the default.
Detailed command line interface reference: `jadeite.exe [game path] <launcher path> <game args...>`, where:
- `game path` - Wine path to the game (required)
- `launcher path` - Wine path to the launcher process (optional, default is `C:\Windows\explorer.exe`). The launcher process will be used to start the game. You can specify the path to the official launcher here. Specifying anything other than `explorer.exe` or the official launcher is not recommended. Specify `--` to skip this argument and use the default
- `game args...` - arguments to pass to the game process (optional)
### Internals
This tool consists of three parts: the main injector (`injector`), the launcher payload (`injector/launcher_payload`) and the game payload (`game_payload`).
Example command: `jadeite.exe 'Z:\path\to\game.exe' -- -screen-fullscreen 1`
I am very bad at explaining, so just take a look at the source code. Maybe I'll write a detailed explanation in the future.
## Configuration
These environment variables can be used to configure the behaviour of the tool. Any value except empty string counts as set. `1` will be used in all examples.
A part of the source code is witheld (`game_payload/src/tp6.c`). This is a forced measure to make abuse more difficult.
**Global**:
- `WAIT_BEFORE_RESUME=1` - show a messagebox and wait for user input before resuming the game process. Useful on my side for debugging
### Guildelines
**SR-specific**:
- `BREAK_CRYPTCAT=1` - use the experimental patching method. See [#37](https://codeberg.org/mkrsym1/jadeite/issues/37) for details
## Internals and building
To compile jadeite, you will need meson, mingw and nasm. You can probably install all three using your repository's package manager. Once all dependencies are installed, run `./build.sh` in this directory. The compiled files will be located in `./out`.
A part of the source code is witheld (`game_payload/src/core.c`). This is a forced measure to make abuse more difficult. However, a precompiled blob is provided in the repo. `build.sh` will use it automatically.
All source code except `core.c` is available in this repository. You can take look around.
## Guildelines
1. **Please don't share this project in public.** This might attract unnecessary attention from either the Game Company or the Anticheat Company
2. **Please don't abuse this project for cheating.** We're just trying to play the games through Wine
### Troubleshooting
## Troubleshooting
Please do not report any issues with the Game to the official channels. Use the issue tracker of this repository
### Third-party launchers
Will be written later
## Third-party launchers
- **Honkers Launcher** &mdash; Linux launcher for 3rd ([GitHub](https://github.com/an-anime-team/honkers-launcher) | [Codeberg](https://codeberg.org/an-anime-team/honkers-launcher))
- **The Honkers Railway Launcher** &mdash; Linux launcher for SR ([GitHub](https://github.com/an-anime-team/the-honkers-railway-launcher) | [Codeberg](https://codeberg.org/an-anime-team/the-honkers-railway-launcher))
### Credits
- mkrsym1 &mdash; project leader, reverse engineering
- Yor#1920 &mdash; major help with analyzing network activity
- Some others credited in the source code
## Credits
- **mkrsym1** &mdash; project leader, reverse engineering
- **[EternalStudentDesuKa](https://github.com/EternalStudentDesuKa)** &mdash; major help with analyzing network activity
- **[An Anime Team](https://github.com/an-anime-team)** &mdash; Honkers Launcher and The Honkers Railway Launcher
- Some others (credited in the source code)
License: MIT

View File

@ -1,4 +0,0 @@
# Honkai Impact 3rd logging servers:
0.0.0.0 log-upload-os.hoyoverse.com
0.0.0.0 sg-public-data-api.hoyoverse.com
0.0.0.0 dump.gamesafe.qq.com

36
block_analytics.sh Normal file
View File

@ -0,0 +1,36 @@
#!/usr/bin/env bash
analytics_servers=$(cat <<EOF
# Honkai Impact 3rd analytics servers (glb/sea/tw/kr/jp):
0.0.0.0 log-upload-os.hoyoverse.com
0.0.0.0 sg-public-data-api.hoyoverse.com
0.0.0.0 dump.gamesafe.qq.com
# Honkai Impact 3rd analytics servers (cn):
0.0.0.0 log-upload.mihoyo.com
0.0.0.0 public-data-api.mihoyo.com
0.0.0.0 dump.gamesafe.qq.com
# Honkai Star Rail analytics servers (os)
0.0.0.0 log-upload-os.hoyoverse.com
0.0.0.0 sg-public-data-api.hoyoverse.com
# Honkai Star Rail analytics servers (cn)
0.0.0.0 log-upload.mihoyo.com
0.0.0.0 public-data-api.mihoyo.com
EOF
)
if [[ ! `cat /etc/hosts` == *"$analytics_servers"* ]]; then
echo "Blocking analytics servers. This will require superuser privileges"
echo "$analytics_servers" | pkexec tee -a /etc/hosts 2>&1 >> /dev/null
if test $? -ne 0; then
echo "Could not block analytics servers. Please add the following lines to your /etc/hosts manually:"
echo "$analytics_servers"
fi
else
echo "Analytics servers are already blocked"
fi

View File

@ -1,18 +1,24 @@
#!/usr/bin/env sh
#!/usr/bin/env bash
set -e
strip="x86_64-w64-mingw32-strip"
rm -f jadeite.zip
rm -rf out
sh setup.sh
ninja -C build
sh setup.sh --buildtype=release
meson compile -C build
mkdir out
cp ./build/injector/jadeite.exe ./out
cp ./build/injector/launcher_payload/launcher_payload.dll ./out
cp ./build/injector/launcher_payload.dll ./out
cp ./build/game_payload/game_payload.dll ./out
cp ./block_analytics.sh ./out
cp ./LICENSE.txt ./out
$strip ./out/*.{exe,dll}
if [ "x$1" = "xrelease" ]; then
cd out
zip ../jadeite.zip *

BIN
game_payload/blob/core.o Normal file

Binary file not shown.

View File

@ -0,0 +1,4 @@
#!/usr/bin/env sh
cp "$1" "$2"
cp "$1" "$3"

View File

@ -2,7 +2,9 @@
#include <windows.h>
#include <game.h>
void ace_fake_driver_files();
HMODULE ace_load_base_module(const char *exeName);
HMODULE ace_load_base_module(struct game_data *game);
HMODULE ace_load_driver_module();

View File

@ -0,0 +1 @@
#define JADEITE_VERSION "@version@"

View File

@ -0,0 +1,9 @@
#pragma once
#include <windows.h>
#include <game.h>
void core_setup_patcher(struct game_data *game, HMODULE baseModule, wchar_t *txFile);
void *core_perform_tx(struct game_data *game, size_t *outLength);

View File

@ -8,11 +8,13 @@
/* CRC-32C (iSCSI) polynomial in reversed bit order. */
#define __POLY 0x82f63b78
static inline uint32_t crc32c(uint32_t crc, const unsigned char *buf, size_t len) {
static inline uint32_t crc32c(uint32_t crc, const void *buf, size_t len) {
const unsigned char *cbuf = (const unsigned char*)buf;
crc = ~crc;
while (len--) {
crc ^= *buf++;
crc ^= *cbuf++;
for (int k = 0; k < 8; k++) {
crc = crc & 1 ? (crc >> 1) ^ __POLY : crc >> 1;
}

View File

@ -1,6 +0,0 @@
#pragma once
#include <wchar.h>
void err_mb_a(const char *format, ...);
void err_mb_w(const wchar_t *format, ...);

View File

@ -2,19 +2,20 @@
#include <windows.h>
enum game_id {
GAME_INVALID,
#define INVOKE_CALLBACK(callback, ...) if (callback) { callback(__VA_ARGS__); }
GAME_HI3_GLB
};
typedef void (*unityplayer_callback_t)(HMODULE unityModule);
struct game_data {
enum game_id id; // Temporary
const char *name;
const char *base_module_name;
const char *assembly_path;
const wchar_t *assembly_name_lwr;
const char *tp6_section_name; // Unused for now
const char *txs_section_name;
const char *tvm_section_name;
unityplayer_callback_t unityplayer_callback;
};
void game_detect(struct game_data *buf);
void hi3_fill_data(struct game_data *buf);
void hsr_fill_data(struct game_data *buf);

View File

@ -1,5 +0,0 @@
#pragma once
#include <game.h>
void hi3_fill_data(struct game_data *buf);

View File

@ -0,0 +1,8 @@
#pragma once
#define ISSUE_SUFFIX "Please open an issue on the jadeite repository specifying your game edition/region and version"
void unload_ctr_inc();
void unload_ctr_dec();
void request_restart();

View File

@ -0,0 +1,12 @@
#pragma once
#include <wchar.h>
void __attribute__((noreturn)) msg_err_a(const char *format, ...);
void __attribute__((noreturn)) msg_err_w(const wchar_t *format, ...);
void msg_warn_a(const char *format, ...);
void msg_warn_w(const wchar_t *format, ...);
void msg_info_a(const char *format, ...);
void msg_info_w(const wchar_t *format, ...);

View File

@ -27,10 +27,32 @@ typedef union _LDR_DLL_NOTIFICATION_DATA {
typedef void (*LdrDllNotification_t)(ULONG reason, const PLDR_DLL_NOTIFICATION_DATA data, void *context);
typedef NTSTATUS (*LdrRegisterDllNotification_t)(ULONG flags, LdrDllNotification_t notificationFunction, void *context, void **cookie);
typedef NTSTATUS (*LdrUnregisterDllNotification_t)(void *cookie);
#define DYNAMIC_FN_TYPE(ret, name, args) typedef ret (*name##_t)args
extern LdrRegisterDllNotification_t LdrRegisterDllNotification;
extern LdrUnregisterDllNotification_t LdrUnregisterDllNotification;
#ifdef NTDLL_DYNAMIC_LINK_IMPL
#define DYNAMIC_FN_VAR(name) extern name##_t name; name##_t name
#else
#define DYNAMIC_FN_VAR(name) extern name##_t name
#endif
void ntdll_link();
#define DYNAMIC_FN_DEF(ret, name, args) DYNAMIC_FN_TYPE(ret, name, args); DYNAMIC_FN_VAR(name)
DYNAMIC_FN_DEF(NTSTATUS, LdrRegisterDllNotification, (ULONG flags, LdrDllNotification_t notification, void *context, void **cookie));
DYNAMIC_FN_DEF(NTSTATUS, LdrUnregisterDllNotification, (void *cookie));
#ifdef NTDLL_DYNAMIC_LINK_IMPL
#define DYNAMIC_FN_LINK(module, name) name = (name##_t)GetProcAddress(module, #name)
static void _ntdll_link() {
HMODULE ntdll = GetModuleHandleA("ntdll.dll");
DYNAMIC_FN_LINK(ntdll, LdrRegisterDllNotification);
DYNAMIC_FN_LINK(ntdll, LdrUnregisterDllNotification);
}
#undef DYNAMIC_FN_LINK
#endif
#undef DYNAMIC_FN_TYPE
#undef DYNAMIC_FN_VAR
#undef DYNAMIC_FN_DEF

View File

@ -2,5 +2,6 @@
#include <windows.h>
void pe_find_section(HMODULE module, const char *section, MEMORY_BASIC_INFORMATION *buf);
IMAGE_SECTION_HEADER *pe_find_section(const void *module, const char *section);
void *pe_find_entry_point(HMODULE module);

View File

@ -1,7 +0,0 @@
#pragma once
#include <windows.h>
#include <game.h>
void tp6_setup_patcher(struct game_data *game, HMODULE thisModule, HMODULE baseModule);

View File

@ -0,0 +1,3 @@
#include <game.h>
void tx_table_file(struct game_data *game, wchar_t *buf);

View File

@ -2,4 +2,15 @@
#include <stdint.h>
uint32_t utils_file_crc32c(const char *filePath);
#define UTILS_COUNT(arr) (sizeof(arr) / sizeof(*arr))
int utils_path_exists(const wchar_t *filePath);
uint32_t utils_file_crc32c(const wchar_t *filePath);
void utils_create_parent_dirs(const wchar_t *path);
void utils_save_to_file(const wchar_t *filePath, const void *buf, size_t length);
char utils_env_enabled(const char *env);
void utils_write_protected_memory(void *addr, const void *buf, size_t size);

View File

@ -1,34 +1,62 @@
fs = import('fs')
include_dir = include_directories('include')
# Input files
sources = [
'src/main.c',
'src/ntdll.c',
'src/ace.c',
'src/pe.c',
'src/game.c',
'src/hi3.c',
'src/utils.c',
'src/err.c',
'src/msg.c',
'src/tx.c',
# File withheld to make abuse more difficult
'src/tp6.c'
]
resources = [
'res/hi3/glb/allocations.dat',
'res/hi3/glb/entries.dat'
'src/hi3/hi3.c',
'src/hsr/hsr.c'
]
# Generate resource files for ./res
res_files = custom_target(
'resources.[ho]',
output: [ 'resources.o', 'resources.h' ],
input: resources,
command: [ gen_res, meson.current_source_dir(), '@OUTPUT0@', '@OUTPUT1@', '@INPUT@' ]
)
if fs.exists('src/core.c')
# Compile the real file first (dirty hack)
core_fake_exe = executable(
'core.o',
'src/core.c',
link_args: [ '-r' ], # Output an object file
include_directories: include_dir
)
# another dirty hack
copy_core = find_program('copy_core.sh')
core_target = [custom_target(
'copy_core',
output: 'core.o',
input: core_fake_exe.extract_all_objects(recursive: false),
command: [
copy_core,
'@INPUT0@',
'@OUTPUT0@', meson.current_source_dir() / 'blob/core.o'
]
)]
core_blob = []
else
message('Using precompiled core blob. Refer to the readme for more details')
core_target = []
core_blob = [ 'blob/core.o' ]
endif
conf_data = configuration_data()
conf_data.set('version', meson.project_version())
conf = configure_file(input: 'include/config.h.in', output: 'config.h', configuration: conf_data)
shared_library(
'game_payload',
sources,
res_files,
include_directories: 'include',
core_target,
conf,
objects: core_blob,
include_directories: include_dir,
name_prefix: ''
)

Binary file not shown.

View File

@ -1,6 +1,7 @@
#include <ntdll.h>
#include <pe.h>
#include <err.h>
#include <msg.h>
#include <utils.h>
#include <ace.h>
@ -9,14 +10,13 @@ static void _dll_notification(ULONG reason, const PLDR_DLL_NOTIFICATION_DATA dat
return;
}
// context should be set to the target module name, lowercase
// context should be set to the target module name
wchar_t *targetModuleName = (wchar_t*)context;
wchar_t lwModuleName[MAX_PATH];
wcscpy(lwModuleName, data->Loaded.BaseDllName->Buffer);
_wcslwr(lwModuleName);
if (wcsicmp(targetModuleName, data->Loaded.BaseDllName->Buffer) != 0) {
return;
}
if (wcscmp(targetModuleName, lwModuleName) == 0) {
// Replace entry point with a stub
void *entryPoint = pe_find_entry_point(data->Loaded.DllBase);
@ -24,68 +24,53 @@ static void _dll_notification(ULONG reason, const PLDR_DLL_NOTIFICATION_DATA dat
0xB8, 0x01, 0x00, 0x00, 0x00, // mov eax, 1
0xC3 // ret
};
utils_write_protected_memory(entryPoint, ENTRY_POINT_STUB, sizeof(ENTRY_POINT_STUB));
}
DWORD oldProtect;
VirtualProtect(entryPoint, sizeof(ENTRY_POINT_STUB), PAGE_EXECUTE_READWRITE, &oldProtect);
static void _create_driver_file(const char *path) {
// They only report presence
HANDLE file = CreateFileA(path, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
memcpy(entryPoint, ENTRY_POINT_STUB, sizeof(ENTRY_POINT_STUB));
VirtualProtect(entryPoint, sizeof(ENTRY_POINT_STUB), oldProtect, &oldProtect);
if (file == INVALID_HANDLE_VALUE) {
msg_err_a("Could not create driver file: %s", path);
}
CloseHandle(file);
}
void ace_fake_driver_files() {
// They only report presence
const char *wdDriverPath = "ACE-BASE.sys";
const char *s32DriverPath = "C:\\windows\\system32\\drivers\\ACE-BASE.sys";
HANDLE wdDriverFile = CreateFileA(wdDriverPath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (!wdDriverFile) {
err_mb_a("Could not create driver file: %s", wdDriverPath);
}
_create_driver_file("ACE-BASE.sys");
// Just in case
HANDLE s32DriverFile = CreateFileA(s32DriverPath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (!s32DriverFile) {
err_mb_a("Could not create driver file: %s", s32DriverPath);
}
CloseHandle(wdDriverFile);
CloseHandle(s32DriverFile);
_create_driver_file("C:\\windows\\system32\\drivers\\ACE-BASE.sys");
}
HMODULE ace_load_base_module(const char *exeName) {
wchar_t baseModuleName[MAX_PATH];
swprintf(baseModuleName, MAX_PATH, L"%sbase.dll", exeName);
wcslwr(baseModuleName);
static HMODULE _load_module_patched(wchar_t *path) {
// Get filename from the path
wchar_t *name = wcsrchr(path, '\\');
name = name ? name + 1 : path;
void *cookie;
LdrRegisterDllNotification(0, &_dll_notification, baseModuleName, &cookie);
LdrRegisterDllNotification(0, &_dll_notification, name, &cookie);
HMODULE baseModule = LoadLibraryW(baseModuleName);
if (!baseModule) {
err_mb_w(L"Could not load base module: %ls", baseModuleName);
HMODULE module = LoadLibraryW(path);
if (!module) {
msg_err_w(L"Could not load module: %ls", path);
}
// LoadLibraryA is synchronous; the notification function has already finished executing
// LoadLibraryW is synchronous; the notification function has already finished executing
LdrUnregisterDllNotification(cookie);
return baseModule;
return module;
}
HMODULE ace_load_base_module(struct game_data *game) {
wchar_t baseModuleName[MAX_PATH];
MultiByteToWideChar(CP_UTF8, 0, game->base_module_name, strlen(game->base_module_name) + 1, baseModuleName, MAX_PATH);
return _load_module_patched(baseModuleName);
}
HMODULE ace_load_driver_module() {
const char *driverModulePath = "AntiCheatExpert/InGame/x64/ACE-DRV64.dll";
void *cookie;
LdrRegisterDllNotification(0, &_dll_notification, L"ace-drv64.dll", &cookie);
HMODULE driverModule = LoadLibraryA(driverModulePath);
if (!driverModule) {
err_mb_a("Could not load driver module: %s", driverModulePath);
}
// LoadLibraryA is synchronous; the notification function has already finished executing
LdrUnregisterDllNotification(cookie);
return driverModule;
return _load_module_patched(L"AntiCheatExpert\\InGame\\x64\\ACE-DRV64.dll");
}

31
game_payload/src/core.md Normal file
View File

@ -0,0 +1,31 @@
### 1.0.0
- First version
### 1.1.0
- HSR support
### 1.1.9
- Fixed a bug which could cause the game to crash in odd scenarios
### 1.1.10
- Fixed a subtle bug introduced in 1.1.9
### 1.1.11
- Fixed an additional issue introduced in 1.1.9
### 2.0.0
- Almost a full rewrite, functionality unchanged
- Added support for HI3 sea/cn/tw/jp/kr
### 3.0.0
- Integrated table extractor
### 3.0.1
- Fixed a bug that caused HI3 to crash
### 3.0.2
- Fixed multiple error messageboxes showing invalid characters
- Added handling for more error conditions
### 3.0.4
- Moved LoadLibrary call into core from main

View File

@ -1,26 +0,0 @@
#include <windows.h>
#include <stdio.h>
#include <err.h>
#define DEF_ERROR_FN(name, type, printfn, mbfn, projname) \
void name(const type *format, ...) { \
va_list args; \
va_start(args, format); \
\
int count = printfn(NULL, 0, format, args) + 1; \
\
type *buf = malloc(count * sizeof(type)); \
printfn(buf, count, format, args); \
\
mbfn(NULL, buf, projname, MB_OK | MB_ICONERROR); \
\
va_end(args); \
\
free(buf); \
exit(1); \
}
DEF_ERROR_FN(err_mb_a, char, _vsnprintf, MessageBoxA, "Jadeite Autopatcher")
DEF_ERROR_FN(err_mb_w, wchar_t, _vsnwprintf, MessageBoxW, L"Jadeite Autopatcher")

View File

@ -1,19 +1,39 @@
#include <err.h>
#include <hi3.h>
#include <msg.h>
#include <utils.h>
#include <game.h>
typedef void (*fill_fn)(struct game_data *buf);
struct name_fn_pair {
const wchar_t *name;
fill_fn fill;
};
const struct name_fn_pair GAMES[] = {
{ L"BH3", &hi3_fill_data },
{ L"StarRail", &hsr_fill_data }
};
void game_detect(struct game_data *buf) {
wchar_t exePath[MAX_PATH];
GetModuleFileNameW(NULL, exePath, MAX_PATH);
// Leave only the basename
wchar_t *exeName = wcsrchr(exePath, L'\\') + 1;
wcslwr(exeName);
// Only HI3 is supported for now
if (wcscmp(exeName, L"bh3.exe") == 0) {
hi3_fill_data(buf);
} else {
err_mb_w(L"Unknown game: %ls", exeName);
// Cut off extension (.exe)
wchar_t *extensionDot = wcsrchr(exeName, L'.');
if (extensionDot != NULL) {
*extensionDot = L'\0';
}
for (size_t i = 0; i < UTILS_COUNT(GAMES); i++) {
if (wcsicmp(exeName, GAMES[i].name) == 0) {
GAMES[i].fill(buf);
return;
}
}
msg_err_w(L"Unknown game: %ls", exeName);
}

View File

@ -1,44 +0,0 @@
#include <utils.h>
#include <err.h>
#include <hi3.h>
const char *HI3_NAME = "BH3";
const char *HI3_ASSEMBLY_PATH = "BH3_Data/Native/UserAssembly.dll";
const wchar_t *HI3_ASSEMBLY_NAME_LWR = L"userassembly.dll";
const char *HI3_TP6_SECTION_NAME = ".bh3";
const char *HI3_TVM_SECTION_NAME = ".tvm0";
struct crc_id_pair {
uint32_t crc;
enum game_id id;
};
const struct crc_id_pair HI3_REGIONS[] = {
// Only glb for now
// It may be possible to get rid of region-specific data altogether in the future
{ 0x34bdec99, GAME_HI3_GLB } // glb v6.6.0
};
void hi3_fill_data(struct game_data *buf) {
uint32_t crc = utils_file_crc32c("UnityPlayer.dll");
enum game_id id = GAME_INVALID;
for (size_t i = 0; i < sizeof(HI3_REGIONS) / sizeof(struct crc_id_pair); i++) {
if (HI3_REGIONS[i].crc == crc) {
id = HI3_REGIONS[i].id;
}
}
if (id == GAME_INVALID) {
err_mb_a("Invalid UnityPlayer.dll checksum: %d", crc);
}
buf->id = id;
buf->name = HI3_NAME;
buf->assembly_path = HI3_ASSEMBLY_PATH;
buf->assembly_name_lwr = HI3_ASSEMBLY_NAME_LWR;
buf->tp6_section_name = HI3_TP6_SECTION_NAME;
buf->tvm_section_name = HI3_TVM_SECTION_NAME;
}

View File

@ -0,0 +1,21 @@
#include <utils.h>
#include <msg.h>
#include <pe.h>
#include <main.h>
#include <game.h>
const char *HI3_BASE_MODULE_NAME = "BH3Base.dll";
const char *HI3_ASSEMBLY_PATH = "BH3_Data\\Native\\UserAssembly.dll";
const char *HI3_TXS_SECTION_NAME = ".ace";
const char *HI3_TVM_SECTION_NAME = ".tvm0";
void hi3_fill_data(struct game_data *buf) {
buf->base_module_name = HI3_BASE_MODULE_NAME;
buf->assembly_path = HI3_ASSEMBLY_PATH;
buf->txs_section_name = HI3_TXS_SECTION_NAME;
buf->tvm_section_name = HI3_TVM_SECTION_NAME;
buf->unityplayer_callback = NULL;
}

122
game_payload/src/hsr/hsr.c Normal file
View File

@ -0,0 +1,122 @@
#include <utils.h>
#include <msg.h>
#include <main.h>
#include <game.h>
const char *HSR_BASE_MODULE_NAME = "StarRailBase.dll";
const char *HSR_ASSEMBLY_PATH = "GameAssembly.dll";
const char *HSR_TXS_SECTION_NAME = ".ace";
const char *HSR_TVM_SECTION_NAME = ".tvm0";
#define HSR_VERSION "2.0.0"
enum hsr_region {
HSR_INVALID,
HSR_OS,
HSR_CN
};
struct crc_region_pair {
uint32_t crc;
enum hsr_region id;
};
const struct crc_region_pair HSR_REGIONS[] = {
{ 0x5be3a560, HSR_OS }, // os v2.0.0
{ 0x974e826f, HSR_CN } // cn v2.0.0
};
#define JUMP_SIZE (6 + sizeof(void*))
// Temporarily hardcoded offset
// v2.0.0, same for os and cn
#define WTSUD_PATCH_OFFSET 0x16510
char wtsud_original_bytes[JUMP_SIZE];
char *wtsud_patch_addr;
static void _wtsud_stub() {
// Recover original bytes
utils_write_protected_memory(wtsud_patch_addr, wtsud_original_bytes, JUMP_SIZE);
unload_ctr_dec();
}
static void _unityplayer_callback(HMODULE unityModule) {
if (utils_env_enabled("CHECKSUM_PATCH_DISABLE")) {
msg_info_a("DirectX library verification patch disabled. The game will not work");
return;
}
// Remove check by patching WriteTextureStatisticUserData
unload_ctr_inc();
wtsud_patch_addr = ((char*)unityModule) + WTSUD_PATCH_OFFSET;
// Save original bytes
memcpy(wtsud_original_bytes, wtsud_patch_addr, JUMP_SIZE);
// Prepare payload
char payload[JUMP_SIZE];
const char JUMP_INST[] = { 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00 }; // jmp [$ + 6]
memcpy(payload, JUMP_INST, sizeof(JUMP_INST));
void *destAddr = &_wtsud_stub;
memcpy(payload + sizeof(JUMP_INST), &destAddr, sizeof(destAddr));
// Write payload
utils_write_protected_memory(wtsud_patch_addr, payload, sizeof(payload));
}
static void _break_cryptcat() {
const char STUB[] = {
0xB8, 0x01, 0x00, 0x00, 0x00, // mov eax, 1
0xC3 // ret
};
const char *STUB_FUNCTIONS[] = {
"CryptCATAdminEnumCatalogFromHash",
"CryptCATCatalogInfoFromContext",
"CryptCATAdminReleaseCatalogContext"
};
HMODULE wintrust = LoadLibraryA("wintrust.dll");
for (size_t i = 0; i < UTILS_COUNT(STUB_FUNCTIONS); i++) {
void *fn = GetProcAddress(wintrust, STUB_FUNCTIONS[i]);
utils_write_protected_memory(fn, STUB, sizeof(STUB));
}
}
void hsr_fill_data(struct game_data *buf) {
if (!utils_env_enabled("BREAK_CRYPTCAT")) {
uint32_t crc = utils_file_crc32c(L"UnityPlayer.dll");
enum hsr_region id = HSR_INVALID;
for (size_t i = 0; i < UTILS_COUNT(HSR_REGIONS); i++) {
if (HSR_REGIONS[i].crc == crc) {
id = HSR_REGIONS[i].id;
break;
}
}
if (id == HSR_INVALID) {
msg_err_a("Invalid UnityPlayer.dll checksum: 0x%08x. This patch is intended to be used with HSR v" HSR_VERSION, crc);
}
// WriteTextureStatisticUserData patch
buf->unityplayer_callback = &_unityplayer_callback;
} else {
msg_warn_a("Using experimental patching method");
_break_cryptcat();
buf->unityplayer_callback = NULL;
}
buf->base_module_name = HSR_BASE_MODULE_NAME;
buf->assembly_path = HSR_ASSEMBLY_PATH;
buf->txs_section_name = HSR_TXS_SECTION_NAME;
buf->tvm_section_name = HSR_TVM_SECTION_NAME;
}

View File

@ -1,10 +1,73 @@
#include <windows.h>
#include <ntdll.h>
#include <ace.h>
#include <game.h>
#include <tp6.h>
#include <core.h>
#include <utils.h>
#include <msg.h>
#include <tx.h>
#define NTDLL_DYNAMIC_LINK_IMPL
#include <ntdll.h>
#include <main.h>
HMODULE this_module;
size_t unload_ctr = 0;
void unload_ctr_inc() {
unload_ctr++;
}
void unload_ctr_dec() {
unload_ctr--;
if (unload_ctr == 0) {
void *pFreeLibrary = GetProcAddress(GetModuleHandleA("kernel32.dll"), "FreeLibrary");
CreateThread(NULL, 0, pFreeLibrary, this_module, 0, NULL);
}
}
void request_restart() {
wchar_t restartFlagFile[MAX_PATH];
GetTempPathW(MAX_PATH, restartFlagFile);
wcscat(restartFlagFile, L"jadeite\\restart_flag");
HANDLE hRestartFlag = CreateFileW(restartFlagFile, FILE_WRITE_ACCESS, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
CloseHandle(hRestartFlag);
}
static void _run_game(struct game_data *game, wchar_t *txFile) {
// Create fake ACE driver files
ace_fake_driver_files();
// Load both ACE modules
HMODULE baseModule = ace_load_base_module(game);
ace_load_driver_module();
// ...magic
core_setup_patcher(game, baseModule, txFile);
// Load the UnityPlayer module and invoke the callback
HMODULE unityModule = LoadLibraryA("UnityPlayer.dll");
INVOKE_CALLBACK(game->unityplayer_callback, unityModule);
}
static void _run_tx(struct game_data *game, wchar_t *txFile) {
// ...more magic
size_t tableSize;
void *table = core_perform_tx(game, &tableSize);
// Save to file
utils_create_parent_dirs(txFile);
utils_save_to_file(txFile, table, tableSize);
// Cleanup
free(table);
// The file should now exist: restart and launch the game
request_restart();
exit(0);
}
BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved) {
// Only listen to attach
@ -12,22 +75,24 @@ BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved) {
return TRUE;
}
this_module = instance;
// Dynamically link functions from ntdll
ntdll_link();
_ntdll_link();
// Detect which game the user is trying to run
struct game_data game;
game_detect(&game);
// Create fake ACE driver files
ace_fake_driver_files();
// Get required table file path
wchar_t txFile[MAX_PATH];
tx_table_file(&game, txFile);
// Load both ACE modules
HMODULE baseModule = ace_load_base_module(game.name);
ace_load_driver_module();
// ...magic
tp6_setup_patcher(&game, instance, baseModule);
if (utils_path_exists(txFile)) {
_run_game(&game, txFile);
} else {
_run_tx(&game, txFile);
}
return TRUE;
}

38
game_payload/src/msg.c Normal file
View File

@ -0,0 +1,38 @@
#include <windows.h>
#include <stdio.h>
#include <config.h>
#include <msg.h>
#define DEF_MSG_FN(name, type, printfn, mbfn, projname, flags, suffix) \
void name(const type *format, ...) { \
va_list args; \
va_start(args, format); \
\
int count = printfn(NULL, 0, format, args) + 1; \
\
type *buf = malloc(count * sizeof(type)); \
printfn(buf, count, format, args); \
\
mbfn(NULL, buf, projname, flags); \
\
va_end(args); \
\
free(buf); \
suffix; \
}
const char *TITLE_A = "v" JADEITE_VERSION " Jadeite Autopatcher";
const wchar_t *TITLE_W = L"v" JADEITE_VERSION " Jadeite Autopatcher";
// Error
DEF_MSG_FN(msg_err_a, char, _vsnprintf, MessageBoxA, TITLE_A, MB_OK | MB_ICONERROR, exit(1))
DEF_MSG_FN(msg_err_w, wchar_t, _vsnwprintf, MessageBoxW, TITLE_W, MB_OK | MB_ICONERROR, exit(1))
// Warn
DEF_MSG_FN(msg_warn_a, char, _vsnprintf, MessageBoxA, TITLE_A, MB_OK | MB_ICONEXCLAMATION,)
DEF_MSG_FN(msg_warn_w, wchar_t, _vsnwprintf, MessageBoxW, TITLE_W, MB_OK | MB_ICONEXCLAMATION,)
// Info
DEF_MSG_FN(msg_info_a, char, _vsnprintf, MessageBoxA, TITLE_A, MB_OK | MB_ICONINFORMATION,)
DEF_MSG_FN(msg_info_w, wchar_t, _vsnwprintf, MessageBoxW, TITLE_W, MB_OK | MB_ICONINFORMATION,)

View File

@ -1,11 +0,0 @@
#include <ntdll.h>
LdrRegisterDllNotification_t LdrRegisterDllNotification;
LdrUnregisterDllNotification_t LdrUnregisterDllNotification;
void ntdll_link() {
HMODULE ntdll = GetModuleHandleA("ntdll.dll");
LdrRegisterDllNotification = (LdrRegisterDllNotification_t)GetProcAddress(ntdll, "LdrRegisterDllNotification");
LdrUnregisterDllNotification = (LdrUnregisterDllNotification_t)GetProcAddress(ntdll, "LdrUnregisterDllNotification");
}

View File

@ -1,27 +1,23 @@
#include <stdint.h>
#include <pe.h>
void pe_find_section(HMODULE module, const char *section, MEMORY_BASIC_INFORMATION *buf) {
char *cModule = (char*)module;
IMAGE_SECTION_HEADER *pe_find_section(const void *module, const char *section) {
const char *cModule = (const char*)module;
IMAGE_DOS_HEADER* dosHeader = (IMAGE_DOS_HEADER*)module;
IMAGE_NT_HEADERS64* ntHeaders = (IMAGE_NT_HEADERS64*)(cModule + dosHeader->e_lfanew);
uint16_t sectionCount = ntHeaders->FileHeader.NumberOfSections;
WORD sectionCount = ntHeaders->FileHeader.NumberOfSections;
IMAGE_SECTION_HEADER* sectionHeader = (IMAGE_SECTION_HEADER*)(ntHeaders + 1);
void* targetAddress = 0x0;
for (uint16_t i = 0; i < sectionCount; i++) {
for (WORD i = 0; i < sectionCount; i++) {
if (strncmp((char*)sectionHeader->Name, section, 8) == 0) {
targetAddress = (void*)(cModule + sectionHeader->VirtualAddress);
break;
return sectionHeader;
}
sectionHeader++;
}
VirtualQuery(targetAddress, buf, sizeof(MEMORY_BASIC_INFORMATION));
return NULL;
}
void *pe_find_entry_point(HMODULE module) {

View File

@ -1,2 +0,0 @@
### 1.0.0
- First version

44
game_payload/src/tx.c Normal file
View File

@ -0,0 +1,44 @@
#include <windows.h>
#include <stdio.h>
#include <crc32.h>
#include <msg.h>
#include <pe.h>
#include <main.h>
#include <config.h>
#include <tx.h>
void tx_table_file(struct game_data *game, wchar_t *buf) {
// Get temp directory path
wchar_t tempDir[MAX_PATH];
GetTempPathW(MAX_PATH, tempDir);
// Memorymap the base module
HANDLE baseFile = CreateFileA(game->base_module_name, FILE_READ_ACCESS, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (!baseFile) {
msg_err_a("Could not open file: %s", game->base_module_name);
}
HANDLE hBaseMap = CreateFileMappingA(baseFile, NULL, PAGE_READONLY, 0, 0, NULL);
char *baseMap = MapViewOfFile(hBaseMap, FILE_MAP_READ, 0, 0, 0);
if (!baseMap) {
msg_err_a("Could not create file mapping for %s", game->base_module_name);
}
// Checksum the TXS section
IMAGE_SECTION_HEADER *txsSection = pe_find_section(baseMap, game->txs_section_name);
if (!txsSection) {
msg_err_a("Could not find %s in %s. " ISSUE_SUFFIX, game->txs_section_name, game->base_module_name);
}
uint32_t txsChecksum = crc32c(0, baseMap + txsSection->PointerToRawData, txsSection->SizeOfRawData);
// Format the path
wsprintfW(buf, L"%sjadeite\\" JADEITE_VERSION "\\%hs.%x.dat", tempDir, game->base_module_name, txsChecksum);
// Cleanup
UnmapViewOfFile(baseMap);
CloseHandle(hBaseMap);
CloseHandle(baseFile);
}

View File

@ -1,14 +1,18 @@
#include <windows.h>
#include <crc32.h>
#include <err.h>
#include <msg.h>
#include <utils.h>
uint32_t utils_file_crc32c(const char *filePath) {
HANDLE file = CreateFileA(filePath, FILE_READ_ACCESS, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
int utils_path_exists(const wchar_t *filePath) {
return GetFileAttributesW(filePath) != INVALID_FILE_ATTRIBUTES;
}
uint32_t utils_file_crc32c(const wchar_t *filePath) {
HANDLE file = CreateFileW(filePath, FILE_READ_ACCESS, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (!file) {
err_mb_a("Could not open file: %s", filePath);
msg_err_w(L"Could not open file: %ls", filePath);
}
LARGE_INTEGER fileSize;
@ -17,10 +21,10 @@ uint32_t utils_file_crc32c(const char *filePath) {
HANDLE hMap = CreateFileMappingA(file, NULL, PAGE_READONLY, 0, 0, NULL);
char *map = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
if (!map) {
err_mb_a("Could not create file mapping for %s", filePath);
msg_err_w(L"Could not create file mapping for %ls", filePath);
}
uint32_t crc = crc32c(0, (unsigned char*)map, fileSize.QuadPart);
uint32_t crc = crc32c(0, map, fileSize.QuadPart);
UnmapViewOfFile(map);
CloseHandle(hMap);
@ -28,3 +32,44 @@ uint32_t utils_file_crc32c(const char *filePath) {
return crc;
}
// https://stackoverflow.com/a/16719260
void utils_create_parent_dirs(const wchar_t *path) {
wchar_t dir[MAX_PATH];
ZeroMemory(dir, sizeof(dir));
const wchar_t *end = path - 1;
while((end = wcschr(++end, L'\\')) != NULL) {
wcsncpy(dir, path, end - path + 1);
if (!utils_path_exists(dir) && !CreateDirectoryW(dir, NULL)) {
msg_err_w(L"Failed to create directory: %ls", dir);
}
}
}
void utils_save_to_file(const wchar_t *filePath, const void *buf, size_t length) {
HANDLE file = CreateFileW(filePath, FILE_WRITE_ACCESS, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
if (!file) {
msg_err_w(L"Could not open file: %ls", filePath);
}
WriteFile(file, buf, length, NULL, FALSE);
CloseHandle(file);
}
char utils_env_enabled(const char *env) {
char *envText = getenv(env);
return envText && *envText;
}
void utils_write_protected_memory(void *addr, const void *buf, size_t size) {
DWORD oldProtect;
VirtualProtect(addr, size, PAGE_READWRITE, &oldProtect);
memcpy(addr, buf, size);
VirtualProtect(addr, size, oldProtect, &oldProtect);
}

View File

@ -2,19 +2,38 @@
linker="x86_64-w64-mingw32-ld"
# Select output types
for i in {0..1}
do
case "$1" in
--header)
gen_header=1
shift
;;
--object)
gen_object=1
shift
;;
esac
done
# Read project directory
proj_dir=`realpath "$1"`
shift
# Read output file destinations
resources_o=`realpath "$1"`
shift
resources_h=`realpath "$1"`
shift
# Read output file destinations and make sure they don't exist
if [ "x${gen_object}" = "x1" ]; then
resources_o=`realpath "$1"`
shift
# Make sure that the header does not exist
rm -f "${resources_h}"
rm -f "${resources_o}"
rm -f "${resources_h}"
fi
if [ "x${gen_header}" = "x1" ]; then
resources_h=`realpath "$1"`
shift
rm -f "${resources_o}"
fi
# Recomupte relative paths to parameters
idx=0
@ -26,16 +45,19 @@ do
idx="$(("${idx}" + 1))"
done
# Create the object file
pushd "${proj_dir}" >> /dev/null
$linker -r -b binary -o "${resources_o}" "${resource_files[@]}"
popd >> /dev/null
if [ "x${gen_object}" = "x1" ]; then
# Create the object file
pushd "${proj_dir}" >> /dev/null
$linker -r -b binary -o "${resources_o}" "${resource_files[@]}"
popd >> /dev/null
fi
# Include stddef.h in the resources header (for size_t)
echo "#include <stddef.h>" >> "${resources_h}"
if [ "x${gen_header}" = "x1" ]; then
# Include stddef.h in the resources header (for size_t)
echo "#include <stddef.h>" >> "${resources_h}"
for resource in "${resource_files[@]}"
do
for resource in "${resource_files[@]}"
do
# Use relative path to the resource as the variable name
var_name="_binary_${resource}"
@ -46,4 +68,5 @@ do
echo "extern void *${var_name}_start;" >> "${resources_h}"
echo "extern void *${var_name}_size;" >> "${resources_h}"
echo "" >> "${resources_h}"
done
done
fi

7
injector/include/envs.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
#define EPFX L"__JADEITE_"
#define ENV_EXE_PATH EPFX"TARGET_EXE_PATH"
#define ENV_DLL_PATH EPFX"INJECT_DLL_PATH"
#define ENV_PROC_CMD EPFX"PROCESS_COMMAND"

View File

@ -1,33 +1,5 @@
BITS 64
main: ; Replacement entry point
push rbp
mov rbp, rsp
sub rsp, 10h + 90h
call GetKernel32ModuleHandle
mov [rbp - 8h], rax ; kernel32.dll
mov rcx, rax
call GetAddressOf_GetProcAddress
mov [rbp - 10h], rax ; *GetProcAddress
mov rcx, [rbp - 8h] ; kernel32.dll
lea rdx, [rel s_LoadLibraryA]
mov rax, [rbp - 10h] ; *GetProcAddress
call rax ; rax = *LoadLibraryA
lea rcx, [rel dllPath]
call rax ; LoadLibraryA(dllPath)
add rsp, 10h + 90h
pop rbp
ret
; https://dennisbabkin.com/blog/?t=how-to-implement-getprocaddress-in-shellcode
GetKernel32ModuleHandle:
mov rax, gs:[60h]
@ -54,15 +26,15 @@ GetAddressOf_GetProcAddress:
mov r10, 41636f7250746547h ; "GetProcA"
mov r11, 0073736572646441h ; "Address\0"
GAO_GPA@1:
.1:
mov r9d, [r8]
lea r9, [rcx + r9]
; Function name comparision
cmp r10, [r9]
jnz GAO_GPA@2
jnz .2
cmp r11, [r9 + 7]
jnz GAO_GPA@2
jnz .2
; Found GetProcAddress
neg rdx
@ -79,20 +51,12 @@ GAO_GPA@1:
mov r10d, [r10 + rdx * 4]
lea rax, [rcx + r10] ; Function address
jmp GAO_GPA@end
jmp .end
GAO_GPA@2:
.2:
add r8, 4
dec rdx
jnz GAO_GPA@1
jnz .1
GAO_GPA@end:
.end:
ret
; Strings
s_LoadLibraryA: db "LoadLibraryA", 0
dllPath:
; This will be filled out by the injector
; Path to the dll to inject into the launcher

View File

@ -0,0 +1,5 @@
#pragma once
#include <windows.h>
void inject(HANDLE process, const void *payload, size_t payloadSize, const wchar_t *dllPath);

View File

@ -1,18 +0,0 @@
# Assemble the payload that will be injected into the game
l_payload_bin = asm_gen.process('src/payload.asm')
# Embed it into the library
l_res_files = custom_target(
'lpayload.[oh]',
output: [ 'lpayload.o', 'lpayload.h' ],
input: [ l_payload_bin ],
command: [ gen_res, './injector/launcher_payload', '@OUTPUT0@', '@OUTPUT1@', '@INPUT@' ]
)
shared_library(
'launcher_payload',
'src/dll.c',
l_res_files,
include_directories: '../include',
name_prefix: ''
)

View File

@ -1,67 +0,0 @@
#include <injshared.h>
#include <lpayload.h>
const char EXE_ENV[] = "JADEITE_TARGET_EXE_PATH";
const char INJECT_DLL_ENV[] = "JADEITE_INJECT_DLL_PATH";
static inline void read_env(const char *env, char *dest, size_t size) {
GetEnvironmentVariableA(env, dest, size);
SetEnvironmentVariableA(env, "");
}
BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, LPVOID reserved) {
// Only listen for attach
if (reason != DLL_PROCESS_ATTACH) {
return TRUE;
}
// Get target EXE path
char targetExe[MAX_PATH];
read_env(EXE_ENV, targetExe, sizeof(targetExe));
// Get the path of the DLL to inject
char injectDll[MAX_PATH];
read_env(INJECT_DLL_ENV, injectDll, sizeof(injectDll));
// Compute the working directory path
char workdir[MAX_PATH];
strcpy(workdir, targetExe);
*(strrchr(workdir, '\\')) = '\0';
// Start the game
STARTUPINFO si;
ZeroMemory(&si, sizeof(si));
PROCESS_INFORMATION pi;
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
if (!CreateProcessA(
targetExe,
NULL,
NULL,
NULL,
FALSE,
CREATE_SUSPENDED,
NULL,
workdir,
&si,
&pi
)) {
exit(1);
}
// Inject
void *payloadStart = &_binary_lpayload_o_p_payload_bin_start;
size_t payloadSize = (size_t)&_binary_lpayload_o_p_payload_bin_size;
inject(pi.hProcess, payloadStart, payloadSize, injectDll);
// Resume the process
ResumeThread(pi.hThread);
// The launcher process should now hang untill the game terminates
WaitForSingleObject(pi.hProcess, INFINITE);
return TRUE;
}

View File

@ -1,137 +0,0 @@
BITS 64
main: ; Replacement entry point
push rbp
mov rbp, rsp
sub rsp, 30h + 90h
call GetKernel32ModuleHandle
mov [rbp - 8h], rax ; kernel32.dll
mov rcx, rax
call GetAddressOf_GetProcAddress
mov [rbp - 10h], rax ; *GetProcAddress
mov rcx, [rbp - 8h] ; kernel32.dll
lea rdx, [rel s_LoadLibraryA]
mov rax, [rbp - 10h] ; *GetProcAddress
call rax ; rax = *LoadLibraryA
mov [rbp - 18h], rax
lea rcx, [rel dllPath]
call rax ; LoadLibraryA(dllPath)
mov rcx, [rbp - 8h] ; kernel32.dll
lea rdx, [rel s_GetModuleHandleA]
mov rax, [rbp - 10h] ; *GetProcAddress
call rax ; rax = *GetModuleHandle
mov rcx, 0
call rax ; rax = .exe base address
mov [rbp - 20h], rax
mov rcx, [rbp - 8h] ; kernel32.dll
lea rdx, [rel s_GetCommandLineW]
mov rax, [rbp - 10h] ; *GetProcAddress
call rax ; rax = *GetCommandLineW
call rax ; rax = command line
mov [rbp - 28h], rax
lea rcx, [rel s_UnityPlayer.dll]
mov rax, [rbp - 18h] ; *LoadLibraryA
call rax ; rax = UnityPlayer.dll
mov rcx, rax
lea rdx, [rel s_UnityMain]
mov rax, [rbp - 10h] ; *GetProcAddress
call rax ; rax = *UnityMain
mov rcx, [rbp - 20h] ; .exe base address
mov rdx, 0 ; hPrevInstance - 0
mov r8, [rbp - 28h] ; command line
mov r9, 1 ; SW_NORMAL
call rax ; UnityMain(...)
add rsp, 30h + 90h
pop rbp
ret
; https://dennisbabkin.com/blog/?t=how-to-implement-getprocaddress-in-shellcode
GetKernel32ModuleHandle:
mov rax, gs:[60h]
mov rax, [rax + 18h]
mov rax, [rax + 20h]
mov rax, [rax]
mov rax, [rax]
mov rax, [rax + 20h]
ret
GetAddressOf_GetProcAddress:
mov eax, [rcx + 3ch]
add rax, rcx
lea rax, [rax + 88h]
mov edx, [rax]
lea rax, [rcx + rdx]
mov edx, [rax + 18h]
mov r8d, [rax + 20h]
lea r8, [rcx + r8]
mov r10, 41636f7250746547h ; "GetProcA"
mov r11, 0073736572646441h ; "Address\0"
GAO_GPA@1:
mov r9d, [r8]
lea r9, [rcx + r9]
; Function name comparision
cmp r10, [r9]
jnz GAO_GPA@2
cmp r11, [r9 + 7]
jnz GAO_GPA@2
; Found GetProcAddress
neg rdx
mov r10d, [rax + 18h]
lea rdx, [r10 + rdx]
mov r10d, [rax + 24h]
lea r10, [rcx + r10]
movzx rdx, word [r10 + rdx * 2]
mov r10d, [rax + 1ch]
lea r10, [rcx + r10]
mov r10d, [r10 + rdx * 4]
lea rax, [rcx + r10] ; Function address
jmp GAO_GPA@end
GAO_GPA@2:
add r8, 4
dec rdx
jnz GAO_GPA@1
GAO_GPA@end:
ret
; Strings
s_LoadLibraryA: db "LoadLibraryA", 0
s_GetModuleHandleA: db "GetModuleHandleA", 0
s_GetCommandLineW: db "GetCommandLineW", 0
s_UnityPlayer.dll: db "UnityPlayer.dll", 0
s_UnityMain: db "UnityMain", 0
dllPath:
; This will be filled out by the launcher payload dll
; Path to the dll to inject into the game

View File

@ -1,21 +1,50 @@
# Assemble the payload that will be injected into the launcher
inj_payload_bin = asm_gen.process('src/payload.asm')
include_dir = include_directories('include')
str_include_dir = join_paths(meson.current_source_dir(), 'include')
# Embed it into the library
inj_res_files = custom_target(
'ipayload.[oh]',
output: [ 'ipayload.o', 'ipayload.h' ],
input: [ inj_payload_bin ],
command: [ gen_res, './injector', '@OUTPUT0@', '@OUTPUT1@', '@INPUT@' ]
# Assemble the payloads
launcher_payload_bin = asm_gen.process(
'src/launcher_p.asm',
extra_args: [ '-i', str_include_dir ]
)
game_payload_bin = asm_gen.process(
'src/game_p.asm',
extra_args: [ '-i', str_include_dir ]
)
# Embed them into .o files
exe_res_files = custom_target(
'launcher_p.[oh]',
output: [ 'launcher_p.o', 'launcher_p.h' ],
input: [ launcher_payload_bin ],
command: [ gen_res, '--header', '--object', './injector', '@OUTPUT0@', '@OUTPUT1@', '@INPUT@' ]
)
dll_res_files = custom_target(
'game_p.[oh]',
output: [ 'game_p.o', 'game_p.h' ],
input: [ game_payload_bin ],
command: [ gen_res, '--header', '--object', './injector', '@OUTPUT0@', '@OUTPUT1@', '@INPUT@' ]
)
# Main injector exe
executable(
'jadeite',
'src/injector.c',
inj_res_files,
include_directories: 'include',
name_prefix: ''
'src/exe.c',
'src/inject.c',
exe_res_files,
include_directories: include_dir,
name_prefix: '',
link_args: '-municode'
)
subdir('launcher_payload')
# Dll that will be injected into the launcher
shared_library(
'launcher_payload',
'src/dll.c',
'src/inject.c',
dll_res_files,
include_directories: include_dir,
name_prefix: '',
link_args: '-municode'
)

121
injector/src/dll.c Normal file
View File

@ -0,0 +1,121 @@
#include <stdio.h>
#include <inject.h>
#include <envs.h>
#include <game_p.h>
typedef char *(*wgufn_t)(wchar_t* path); // wine_get_unix_file_name
const wchar_t *J_MB_TITLE = L"Jadeite Launcher Payload";
BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, LPVOID reserved) {
// Only listen for attach
if (reason != DLL_PROCESS_ATTACH) {
return TRUE;
}
// Get target EXE path
wchar_t targetExe[MAX_PATH];
GetEnvironmentVariableW(ENV_EXE_PATH, targetExe, MAX_PATH);
// Get the path of the DLL to inject
wchar_t injectDll[MAX_PATH];
GetEnvironmentVariableW(ENV_DLL_PATH, injectDll, MAX_PATH);
// Get game commandline
wchar_t cmdline[8192];
GetEnvironmentVariableW(ENV_PROC_CMD, cmdline, sizeof(cmdline) / sizeof(wchar_t));
// Compute the working directory path
wchar_t workdir[MAX_PATH];
wcscpy(workdir, targetExe);
*(wcsrchr(workdir, L'\\')) = L'\0';
// SAFETY: verify that the injector is not inside the game directory
HMODULE kernel32 = GetModuleHandleA("kernel32.dll");
wgufn_t wine_get_unix_file_name = (wgufn_t)GetProcAddress(kernel32, "wine_get_unix_file_name");
if (wine_get_unix_file_name) {
char *unixInjectDll = wine_get_unix_file_name(injectDll);
char *unixWorkdir = wine_get_unix_file_name(workdir);
char *i = unixInjectDll, *w = unixWorkdir;
char startsWith = 0;
while (*i && *w) {
startsWith = *i == *w;
if (!startsWith) break;
i++, w++;
}
HANDLE heap = GetProcessHeap();
HeapFree(heap, 0, unixInjectDll);
HeapFree(heap, 0, unixWorkdir);
if (startsWith) {
MessageBoxW(NULL, L"Putting the patcher (or any other foreign PE binaries) inside the game directory is dangerous! Please move it elsewhere.", J_MB_TITLE, MB_OK | MB_ICONERROR);
exit(1);
}
} else {
MessageBoxW(NULL, L"Could not find wine_get_unix_file_name! Wine version too old?", J_MB_TITLE, MB_OK | MB_ICONWARNING);
}
// Get restart flag file path
wchar_t restartFlagFile[MAX_PATH];
GetTempPathW(MAX_PATH, restartFlagFile);
wcscat(restartFlagFile, L"jadeite\\restart_flag");
do {
// Start the game
STARTUPINFOW si;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
PROCESS_INFORMATION pi;
ZeroMemory(&pi, sizeof(pi));
if (!CreateProcessW(
NULL,
cmdline,
NULL,
NULL,
FALSE,
CREATE_SUSPENDED,
NULL,
workdir,
&si,
&pi
)) {
wchar_t message[1024];
wsprintfW(message, L"Failed to start game process: %ld\nGame executable path: '%ls'", GetLastError(), targetExe);
MessageBoxW(NULL, message, J_MB_TITLE, MB_OK | MB_ICONERROR);
exit(1);
}
// Inject
void *payloadStart = &_binary_game_p_o_p_game_p_bin_start;
size_t payloadSize = (size_t)&_binary_game_p_o_p_game_p_bin_size;
inject(pi.hProcess, payloadStart, payloadSize, injectDll);
// Remove the restart flag file
DeleteFileW(restartFlagFile);
// Optional: wait for user input before resuming (useful for debugging)
char *waitEnabled = getenv("WAIT_BEFORE_RESUME");
if (waitEnabled && *waitEnabled) {
wchar_t message[64];
wsprintfW(message, L"PID: %ld. Press OK to continue", pi.dwProcessId);
MessageBoxW(NULL, message, J_MB_TITLE, MB_OK | MB_ICONINFORMATION);
}
// Resume the process
ResumeThread(pi.hThread);
// The launcher process should now hang untill the game terminates
WaitForSingleObject(pi.hProcess, INFINITE);
} while (GetFileAttributesW(restartFlagFile) != INVALID_FILE_ATTRIBUTES);
return TRUE;
}

120
injector/src/exe.c Normal file
View File

@ -0,0 +1,120 @@
#include <stdio.h>
#include <inject.h>
#include <envs.h>
#include <launcher_p.h>
const wchar_t *LAUNCHER_INJECT_DLL = L"launcher_payload.dll";
const wchar_t *GAME_INJECT_DLL = L"game_payload.dll";
#define SHIFT(argc, argv) argc--, argv++
int wmain(int argc, wchar_t **argv) {
// Read arguments
wchar_t *gamePath = NULL;
wchar_t *launcherPath = NULL;
// Skip executable
SHIFT(argc, argv);
switch (argc) {
case 0:
wprintf(L"Usage: wine jadeite.exe [game path] <launcher path>\n");
return 0;
case 1:
gamePath = argv[0];
SHIFT(argc, argv);
launcherPath = L"--";
break;
default:
gamePath = argv[0];
SHIFT(argc, argv);
launcherPath = argv[0];
SHIFT(argc, argv);
break;
}
// Default launcher path
if (wcscmp(launcherPath, L"--") == 0) {
wprintf(L"No launcher process specified! Using explorer.exe\n");
launcherPath = L"C:\\Windows\\explorer.exe";
}
// cd into the injector directory
wchar_t injectorPath[MAX_PATH];
GetModuleFileNameW(GetModuleHandleW(NULL), injectorPath, MAX_PATH);
*(wcsrchr(injectorPath, L'\\')) = L'\0';
SetCurrentDirectoryW(injectorPath);
// Compute absolute paths
wchar_t gameExePath[MAX_PATH];
GetFullPathNameW(gamePath, MAX_PATH, gameExePath, NULL);
wchar_t gamePayloadPath[MAX_PATH];
GetFullPathNameW(GAME_INJECT_DLL, MAX_PATH, gamePayloadPath, NULL);
wchar_t launcherPayloadPath[MAX_PATH];
GetFullPathNameW(LAUNCHER_INJECT_DLL, MAX_PATH, launcherPayloadPath, NULL);
// Construct commandline for the game process
wchar_t cmdline[8192];
wsprintfW(cmdline, L"\"%ls\"", gameExePath);
while (argc) {
wchar_t arg[8192];
wsprintfW(arg, L" \"%ls\"", argv[0]);
wcscat(cmdline, arg);
SHIFT(argc, argv);
}
// Set envvars
SetEnvironmentVariableW(ENV_EXE_PATH, gameExePath);
SetEnvironmentVariableW(ENV_DLL_PATH, gamePayloadPath);
SetEnvironmentVariableW(ENV_PROC_CMD, cmdline);
// Start the launcher
wprintf(L"Starting '%ls' via '%ls'\n", gameExePath, launcherPath);
STARTUPINFOW si;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
PROCESS_INFORMATION pi;
ZeroMemory(&pi, sizeof(pi));
if (!CreateProcessW(
launcherPath,
NULL,
NULL,
NULL,
FALSE,
CREATE_SUSPENDED,
NULL,
NULL,
&si,
&pi
)) {
fwprintf(stderr, L"Could not start process! (%ld)\n", GetLastError());
exit(1);
}
wprintf(L"Started launcher process (%ld)\n", pi.dwProcessId);
// Inject
void *payloadStart = &_binary_launcher_p_o_p_launcher_p_bin_start;
size_t payloadSize = (size_t)&_binary_launcher_p_o_p_launcher_p_bin_size; // yes this is valid
inject(pi.hProcess, payloadStart, payloadSize, launcherPayloadPath);
// Resume the process
ResumeThread(pi.hThread);
return 0;
}

186
injector/src/game_p.asm Normal file
View File

@ -0,0 +1,186 @@
BITS 64
; Macro definitions
; read dst, pSrc, size
%macro read 3
mov %1, [%2]
add %2, %3
%endmacro
; copy pDst, pSrc, temp, tempSize
%macro copy 4
mov %3, [%2]
mov [%1], %3
add %1, %4
add %2, %4
%endmacro
; unprotect addr, size, fn
%macro unprotect 3
mov rcx, %1
mov rdx, %2
mov r8, 40h ; PAGE_EXECUTE_READWRITE
lea r9, [rel oldProtect]
call %3
%endmacro
; reprotect addr, size, fn
%macro reprotect 3
mov rcx, %1
mov rdx, %2
lea r9, [rel oldProtect]
mov r8d, [r9]
call %3
%endmacro
main: ; Replacement entry point
push rsi
push rdi
push r12
push r13
push r14
call GetKernel32ModuleHandle
mov rsi, rax ; kernel32.dll
mov rcx, rax
call GetAddressOf_GetProcAddress
mov rdi, rax ; *GetProcAddress
mov rcx, rsi ; kernel32.dll
lea rdx, [rel s_VirtualProtect]
call rdi ; rax = *VirtualProtect
mov rcx, rax
call RecoverExecutable
mov rcx, rsi ; kernel32.dll
lea rdx, [rel s_LoadLibraryW]
call rdi ; rax = *LoadLibraryW
lea rcx, [rel dllPath]
call rax ; LoadLibraryW(dllPath)
mov rcx, rsi ; kernel32.dll
lea rdx, [rel s_GetModuleHandleA]
call rdi ; rax = *GetModuleHandle
mov r12, rax
mov rcx, 0
call rax ; rax = .exe base address
mov r13, rax
mov rcx, rsi ; kernel32.dll
lea rdx, [rel s_GetCommandLineW]
call rdi ; rax = *GetCommandLineW
call rax ; rax = command line
mov r14, rax
lea rcx, [rel s_UnityPlayer.dll]
call r12 ; rax = UnityPlayer.dll
mov rcx, rax
lea rdx, [rel s_UnityMain]
call rdi ; rax = *UnityMain
mov rcx, r13 ; .exe base address
mov rdx, 0 ; hPrevInstance - 0
mov r8, r14 ; command line
mov r9, 1 ; SW_NORMAL
call rax ; UnityMain(...)
pop r14
pop r13
pop r12
pop rdi
pop rsi
ret
RecoverExecutable: ; expects *VirtualProtect in rcx
push rbx
push r12
push r13
push r14
sub rsp, 8
mov r13, rcx
; Find the recovery data structure
lea rbx, [rel dllPath]
.search:
read ax, rbx, 2
test ax, ax
jnz .search
; Recover entry point bytes (6 + 8 = 14 total)
read r12, rbx, 8 ; Address
mov r14, r12
unprotect r14, 14, r13
copy r12, rbx, rax, 8
copy r12, rbx, eax, 4
copy r12, rbx, ax, 2
reprotect r14, 14, r13
; Recover import descriptor bytes (20 total)
read r12, rbx, 8
mov r14, r12
unprotect r14, 20, r13
copy r12, rbx, rax, 8
copy r12, rbx, rax, 8
copy r12, rbx, eax, 4
reprotect r14, 20, r13
; Recover import data directory entry size bytes (4 total)
read r12, rbx, 8
mov r14, r12
unprotect r14, 4, r13
copy r12, rbx, eax, 4
reprotect r14, 4, r13
add rsp, 8
pop r14
pop r13
pop r12
pop rbx
ret
%include "gpa.asm"
oldProtect: dd 0
; Strings
s_VirtualProtect: db "VirtualProtect", 0
s_LoadLibraryW: db "LoadLibraryW", 0
s_GetModuleHandleA: db "GetModuleHandleA", 0
s_GetCommandLineW: db "GetCommandLineW", 0
s_UnityPlayer.dll: db "UnityPlayer.dll", 0
s_UnityMain: db "UnityMain", 0
dllPath:
; This will be filled out by the launcher payload dll
; Path to the dll to inject into the game

View File

@ -1,34 +1,41 @@
#include <windows.h>
#include <inject.h>
#define JUMP_SIZE (6 + sizeof(void*))
// Original values to recover after the injection
// Recovery is performed by the assembly payload
#pragma pack(push, 1)
struct recovery_data {
void *entryPointAddress;
char entryPointData[JUMP_SIZE];
void *importDescriptorAddress;
IMAGE_IMPORT_DESCRIPTOR importDescriptorData;
void *sizeFieldAddress;
DWORD sizeFieldData;
};
#pragma pack(pop)
static inline void write_protected_process_memory(HANDLE process, void *address, const void *buf, size_t size) {
DWORD oldProtect;
VirtualProtectEx(process, address, size, PAGE_EXECUTE_READWRITE, &oldProtect);
size_t bytesWritten;
WriteProcessMemory(process, address, buf, size, &bytesWritten);
WriteProcessMemory(process, address, buf, size, NULL);
VirtualProtectEx(process, address, size, oldProtect, &oldProtect);
}
static inline void inject(HANDLE process, const void *payload, size_t payloadSize, const char *dllPath) {
size_t _;
// Inject the loader into the module
size_t dllPathLen = strlen(dllPath) + 1;
char *remoteAlloc = VirtualAllocEx(process, NULL, payloadSize + dllPathLen, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(process, remoteAlloc, payload, payloadSize, &_);
WriteProcessMemory(process, remoteAlloc + payloadSize, dllPath, dllPathLen, &_);
void inject(HANDLE process, const void *payload, size_t payloadSize, const wchar_t *dllPath) {
// Find the EXE header in the process
char exeHeader[1024];
IMAGE_DOS_HEADER *dosHeader;
IMAGE_NT_HEADERS64 *ntHeaders;
IMAGE_DOS_HEADER *dosHeader = NULL;
IMAGE_NT_HEADERS64 *ntHeaders = NULL;
MEMORY_BASIC_INFORMATION memoryInfo;
char *currentAddress = 0x0;
while (VirtualQueryEx(process, currentAddress, &memoryInfo, sizeof(memoryInfo))) {
ReadProcessMemory(process, currentAddress, exeHeader, sizeof(exeHeader), &_);
ReadProcessMemory(process, currentAddress, exeHeader, sizeof(exeHeader), NULL);
dosHeader = (IMAGE_DOS_HEADER*)exeHeader;
@ -45,7 +52,7 @@ static inline void inject(HANDLE process, const void *payload, size_t payloadSiz
}
// Skip DLLs
if ((ntHeaders->FileHeader.Characteristics | IMAGE_FILE_DLL) == IMAGE_FILE_DLL) {
if ((ntHeaders->FileHeader.Characteristics & IMAGE_FILE_DLL) == IMAGE_FILE_DLL) {
goto cont;
}
@ -64,24 +71,60 @@ static inline void inject(HANDLE process, const void *payload, size_t payloadSiz
char *exe = (char*)memoryInfo.BaseAddress;
// Inject the loader into the process
const unsigned char JUMP_INST[] = { 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00 };
size_t dllPathSize = (wcslen(dllPath) + 1) * sizeof(wchar_t);
size_t allocSize = payloadSize + dllPathSize + sizeof(struct recovery_data);
char *remoteAlloc = VirtualAllocEx(process, NULL, allocSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
// Write the assembly payload and dll path
WriteProcessMemory(process, remoteAlloc, payload, payloadSize, NULL);
WriteProcessMemory(process, remoteAlloc + payloadSize, dllPath, dllPathSize, NULL);
// Modify the executable to run the assembly payload
// Recovery data structure
struct recovery_data rd;
// Replace the entry point with a jump to the loader
char *entryPoint = exe + ntHeaders->OptionalHeader.AddressOfEntryPoint;
const unsigned char JUMP_INST[] = { 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00 };
// Save the original entry point address and bytes
rd.entryPointAddress = entryPoint;
ReadProcessMemory(process, rd.entryPointAddress, rd.entryPointData, sizeof(rd.entryPointData), NULL);
// Replace the entry point with a jump to the assembly payload
write_protected_process_memory(process, entryPoint, JUMP_INST, sizeof(JUMP_INST));
write_protected_process_memory(process, entryPoint + sizeof(JUMP_INST), &remoteAlloc, sizeof(remoteAlloc));
// Break the import table to prevent any dlls from being loaded
// Step 1: break the first import descriptor
char *importDescriptors = exe + ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
// Save the original descriptor address and bytes
rd.importDescriptorAddress = importDescriptors;
ReadProcessMemory(process, rd.importDescriptorAddress, &rd.importDescriptorData, sizeof(rd.importDescriptorData), NULL);
// Overwrite with zeroes
IMAGE_IMPORT_DESCRIPTOR firstDescriptor;
ZeroMemory(&firstDescriptor, sizeof(firstDescriptor));
write_protected_process_memory(process, importDescriptors, &firstDescriptor, sizeof(firstDescriptor));
// Step 2: break the image data directory entry
ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size = 0;
char* ddAddr = ((char*)&(ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size)) - exeHeader + exe;
write_protected_process_memory(process, exe, exeHeader, sizeof(exeHeader));
// Save the original value
rd.sizeFieldAddress = ddAddr;
ReadProcessMemory(process, rd.sizeFieldAddress, &rd.sizeFieldData, sizeof(rd.sizeFieldData), NULL);
// Set to 0
DWORD newSize = 0;
write_protected_process_memory(process, ddAddr, &newSize, sizeof(newSize));
// Write recovery data to the allocation
WriteProcessMemory(process, remoteAlloc + payloadSize + dllPathSize, &rd, sizeof(rd), NULL);
}

View File

@ -1,87 +0,0 @@
#include <stdio.h>
#include <injshared.h>
#include <ipayload.h>
const char EXE_ENV[] = "JADEITE_TARGET_EXE_PATH";
const char INJECT_DLL_ENV[] = "JADEITE_INJECT_DLL_PATH";
const char LAUNCHER_INJECT_DLL[] = "launcher_payload.dll";
const char GAME_INJECT_DLL[] = "game_payload.dll";
int main(int argc, char **argv) {
// Read arguments
char *gamePath = NULL;
char *launcherPath = NULL;
switch (argc) {
case 1:
printf("Usage: wine jadeite.exe [game path] <launcher path>\n");
return 0;
case 2:
printf("No launcher process specified! Using explorer.exe\n");
gamePath = argv[1];
launcherPath = "C:\\Windows\\explorer.exe";
break;
case 3:
gamePath = argv[1];
launcherPath = argv[2];
break;
default:
fprintf(stderr, "Too many arguments! (%d)\n", argc);
return 1;
}
// Compute absolute paths
char gameExePath[MAX_PATH];
GetFullPathNameA(gamePath, sizeof(gameExePath), gameExePath, NULL);
char gamePayloadPath[MAX_PATH];
GetFullPathNameA(GAME_INJECT_DLL, sizeof(gamePayloadPath), gamePayloadPath, NULL);
char launcherPayloadPath[MAX_PATH];
GetFullPathNameA(LAUNCHER_INJECT_DLL, sizeof(launcherPayloadPath), launcherPayloadPath, NULL);
printf("Starting \"%s\" via \"%s\"\n", gameExePath, launcherPath);
// Set envvars
SetEnvironmentVariableA(EXE_ENV, gameExePath);
SetEnvironmentVariableA(INJECT_DLL_ENV, gamePayloadPath);
// Start the launcher
STARTUPINFO si;
ZeroMemory(&si, sizeof(si));
PROCESS_INFORMATION pi;
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
if (!CreateProcessA(
launcherPath,
NULL,
NULL,
NULL,
FALSE,
CREATE_SUSPENDED,
NULL,
NULL,
&si,
&pi
)) {
fprintf(stderr, "Could not start process! (%ld)\n", GetLastError());
exit(1);
}
printf("Started launcher process (%ld)\n", pi.dwProcessId);
// Inject
void *payloadStart = &_binary_ipayload_o_p_payload_bin_start;
size_t payloadSize = (size_t)&_binary_ipayload_o_p_payload_bin_size; // yes this is valid
inject(pi.hProcess, payloadStart, payloadSize, launcherPayloadPath);
// Resume the process
ResumeThread(pi.hThread);
return 0;
}

View File

@ -0,0 +1,34 @@
BITS 64
main: ; Replacement entry point
push rsi
call GetKernel32ModuleHandle
mov rsi, rax ; kernel32.dll
mov rcx, rax
call GetAddressOf_GetProcAddress
mov rcx, rsi ; kernel32.dll
lea rdx, [rel s_LoadLibraryW]
call rax ; rax = *LoadLibraryW
lea rcx, [rel dllPath]
call rax ; LoadLibraryA(dllPath)
pop rsi
ret
%include "gpa.asm"
; Strings
s_LoadLibraryW: db "LoadLibraryW", 0
dllPath:
; This will be filled out by the injector
; Path to the dll to inject into the launcher

View File

@ -1,4 +1,4 @@
project('jadeite', 'c', version: '1.0.0')
project('jadeite', 'c', version: '3.1.1')
nasm = find_program('nasm')
gen_res = find_program('gen_resources.sh')
@ -8,6 +8,7 @@ asm_gen = generator(
nasm,
output: '@BASENAME@.bin',
arguments: [
'@EXTRA_ARGS@',
'-f', 'bin',
'@INPUT@',
'-o', '@OUTPUT@'

43
metadata.json Normal file
View File

@ -0,0 +1,43 @@
{
"jadeite": {
"version": "3.1.1"
},
"games": {
"hi3rd": {
"global": {
"status": "verified",
"version": "7.2.0"
},
"sea": {
"status": "verified",
"version": "7.2.0"
},
"china": {
"status": "verified",
"version": "7.3.0"
},
"taiwan": {
"status": "verified",
"version": "7.2.0"
},
"korea": {
"status": "verified",
"version": "7.2.0"
},
"japan": {
"status": "verified",
"version": "7.2.0"
}
},
"hsr": {
"global": {
"status": "unverified",
"version": "2.0.0"
},
"china": {
"status": "unverified",
"version": "2.0.0"
}
}
}
}

View File

@ -1,4 +1,4 @@
#!/usr/bin/env sh
rm -rf build
meson setup --cross-file mingw_cross.txt build
meson setup --cross-file mingw_cross.txt $* build