134 Commits

Author SHA1 Message Date
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
51 changed files with 962 additions and 507 deletions

2
.gitignore vendored
View File

@ -3,7 +3,7 @@
.directory .directory
# File withheld to make abuse more difficult # File withheld to make abuse more difficult
game_payload/src/tp6.c game_payload/src/core.c
build build
out out

View File

@ -1,65 +1,67 @@
### Games and regions # Jadeite Autopatcher
- **3rd**: glb v6.6.0 ### Current game support:
- **SR**: os/cn v1.1.0 (unsafe, refer to [configuration](#configuration)) - **3rd**: glb/sea/tw/kr/jp **v7.1.0+**, cn **v7.2.0+**
- **SR**: os/cn **v1.6.0**
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 ## Information
The anticheat the games use is fundamentally incompatible with Wine in multiple ways. This tool launches the game without it (`injector/launcher_payload`) and imitates it's behaviour (`game_payload`). 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`).
**SR-specific**: this tool disables the use of DirectX shared resources in a rather hacky way. It is required, as there is no (and most likely never will be) shared resources support in DirectX translation layers (WineD3D/DXVK). Refer to [configuration](#configuration) if you wish to run the game without the fix.
**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**. **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**.
**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. **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 ## 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. **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.** **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**: 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. **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: Manual usage instructions:
- Download the game you want to run - 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!**) - 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 `./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'` - 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)
To pass commandline arguments to the game, append them after the launcher path: `wine jadeite.exe 'Z:\wine\path\to\game.exe' 'Z:\wine\path\to\launcher.exe' -arg1 -arg2 -arg3`. To use the default launcher process, use `--`: `wine jadeite.exe 'Z:\wine\path\to\game.exe' -- -arg1 -arg2 -arg3`. Example command: `jadeite.exe 'Z:\path\to\game.exe' -- -screen-fullscreen 1`
### Configuration ## Configuration
These environment variables can be used to configure the behaviour of the tool. 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.
- `WAIT_BEFORE_RESUME=1` - show a messagebox and wait for user input before resuming the game process. Useful on my side for debugging - `WAIT_BEFORE_RESUME=1` - show a messagebox and wait for user input before resuming the game process. Useful on my side for debugging
**SR-exclusive**: ## Internals and building
- `I_WANT_A_BAN=1` - allows to launch SR. Please only use testing accounts, as there is an extremely high risk of getting banned 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`.
- `SRFIX_DISABLE=1` - disable shared resources fix
### Internals 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.
This tool consists of three parts: the main injector (`injector`), the launcher payload (`injector/launcher_payload`) and the game payload (`game_payload`).
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. All source code except `core.c` is available in this repository. You can take look around.
A part of the source code is witheld (`game_payload/src/tp6.c`). This is a forced measure to make abuse more difficult. ## Guildelines
### Guildelines
1. **Please don't share this project in public.** This might attract unnecessary attention from either the Game Company or the Anticheat Company 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 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 Please do not report any issues with the Game to the official channels. Use the issue tracker of this repository
### Third-party launchers ## Third-party launchers
Will be written later - **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 ## Credits
- mkrsym1 &mdash; project leader, reverse engineering - **mkrsym1** &mdash; project leader, reverse engineering
- Yor#1920 &mdash; major help with analyzing network activity - **[EternalStudentDesuKa](https://github.com/EternalStudentDesuKa)** &mdash; major help with analyzing network activity
- Some others credited in the source code - **[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 License: MIT

View File

@ -1,12 +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
# Honkai Star Rail logging servers (oversea)
0.0.0.0 log-upload-os.hoyoverse.com
0.0.0.0 sg-public-data-api.hoyoverse.com
# Honkai Star Rail logging servers (China)
0.0.0.0 log-upload.mihoyo.com
0.0.0.0 public-data-api.mihoyo.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,12 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -e set -e
if ! [ "x$1" = "xdo" ]; then
echo "A part of the source code is witheld (game_payload/src/tp6.c) to make abuse more difficult. Please download a binary release"
exit
fi
shift
strip="x86_64-w64-mingw32-strip" strip="x86_64-w64-mingw32-strip"
rm -f jadeite.zip rm -f jadeite.zip
@ -20,6 +14,7 @@ mkdir out
cp ./build/injector/jadeite.exe ./out cp ./build/injector/jadeite.exe ./out
cp ./build/injector/launcher_payload.dll ./out cp ./build/injector/launcher_payload.dll ./out
cp ./build/game_payload/game_payload.dll ./out cp ./build/game_payload/game_payload.dll ./out
cp ./block_analytics.sh ./out
cp ./LICENSE.txt ./out cp ./LICENSE.txt ./out
$strip ./out/*.{exe,dll} $strip ./out/*.{exe,dll}

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 <windows.h>
#include <game.h>
void ace_fake_driver_files(); 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(); 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. */ /* CRC-32C (iSCSI) polynomial in reversed bit order. */
#define __POLY 0x82f63b78 #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; crc = ~crc;
while (len--) { while (len--) {
crc ^= *buf++; crc ^= *cbuf++;
for (int k = 0; k < 8; k++) { for (int k = 0; k < 8; k++) {
crc = crc & 1 ? (crc >> 1) ^ __POLY : crc >> 1; crc = crc & 1 ? (crc >> 1) ^ __POLY : crc >> 1;
} }

View File

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

View File

@ -1,4 +1,8 @@
#pragma once #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_inc();
void unload_ctr_dec(); void unload_ctr_dec();
void request_restart();

View File

@ -2,8 +2,8 @@
#include <wchar.h> #include <wchar.h>
void msg_err_a(const char *format, ...); void __attribute__((noreturn)) msg_err_a(const char *format, ...);
void msg_err_w(const wchar_t *format, ...); void __attribute__((noreturn)) msg_err_w(const wchar_t *format, ...);
void msg_warn_a(const char *format, ...); void msg_warn_a(const char *format, ...);
void msg_warn_w(const wchar_t *format, ...); void msg_warn_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 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); #define DYNAMIC_FN_TYPE(ret, name, args) typedef ret (*name##_t)args
typedef NTSTATUS (*LdrUnregisterDllNotification_t)(void *cookie);
extern LdrRegisterDllNotification_t LdrRegisterDllNotification; #ifdef NTDLL_DYNAMIC_LINK_IMPL
extern LdrUnregisterDllNotification_t LdrUnregisterDllNotification; #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> #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); 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 baseModule);

View File

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

View File

@ -2,6 +2,15 @@
#include <stdint.h> #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); char utils_env_enabled(const char *env);
void utils_write_protected_memory(void *addr, const void *buf, size_t size);

View File

@ -1,40 +1,62 @@
fs = import('fs')
include_dir = include_directories('include')
# Input files # Input files
sources = [ sources = [
'src/main.c', 'src/main.c',
'src/ntdll.c',
'src/ace.c', 'src/ace.c',
'src/pe.c', 'src/pe.c',
'src/game.c', 'src/game.c',
'src/hi3.c',
'src/hsr.c',
'src/utils.c', 'src/utils.c',
'src/msg.c', 'src/msg.c',
'src/tx.c',
# File withheld to make abuse more difficult 'src/hi3/hi3.c',
'src/tp6.c'
] 'src/hsr/hsr.c'
resources = [
'res/hi3/glb/allocations.dat',
'res/hi3/glb/entries.dat',
'res/hsr/os/allocations.dat',
'res/hsr/os/entries.dat',
'res/hsr/cn/allocations.dat',
'res/hsr/cn/entries.dat'
] ]
# Generate resource files for ./res if fs.exists('src/core.c')
res_files = custom_target( # Compile the real file first (dirty hack)
'resources.[ho]', core_fake_exe = executable(
output: [ 'resources.o', 'resources.h' ], 'core.o',
input: resources, 'src/core.c',
command: [ gen_res, meson.current_source_dir(), '@OUTPUT0@', '@OUTPUT1@', '@INPUT@' ] 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( shared_library(
'game_payload', 'game_payload',
sources, sources,
res_files, core_target,
include_directories: 'include', conf,
objects: core_blob,
include_directories: include_dir,
name_prefix: '' name_prefix: ''
) )

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

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,32 +1,39 @@
#include <msg.h> #include <msg.h>
#include <utils.h>
#include <game.h> #include <game.h>
typedef void (*fill_fn)(struct game_data *buf); typedef void (*fill_fn)(struct game_data *buf);
struct name_fn_pair { struct name_fn_pair {
const char *name; const wchar_t *name;
fill_fn fill; fill_fn fill;
}; };
const struct name_fn_pair GAMES[] = { const struct name_fn_pair GAMES[] = {
{ "bh3.exe", &hi3_fill_data }, { L"BH3", &hi3_fill_data },
{ "starrail.exe", &hsr_fill_data } { L"StarRail", &hsr_fill_data }
}; };
void game_detect(struct game_data *buf) { void game_detect(struct game_data *buf) {
char exePath[MAX_PATH]; wchar_t exePath[MAX_PATH];
GetModuleFileNameA(NULL, exePath, MAX_PATH); GetModuleFileNameW(NULL, exePath, MAX_PATH);
char *exeName = strrchr(exePath, '\\') + 1; // Leave only the basename
strlwr(exeName); wchar_t *exeName = wcsrchr(exePath, L'\\') + 1;
for (size_t i = 0; i < sizeof(GAMES) / sizeof(struct name_fn_pair); i++) { // Cut off extension (.exe)
if (strcmp(exeName, GAMES[i].name) == 0) { 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); GAMES[i].fill(buf);
return; return;
} }
} }
msg_err_a("Unknown game: %s", exeName); msg_err_w(L"Unknown game: %ls", exeName);
} }

View File

@ -1,44 +0,0 @@
#include <utils.h>
#include <msg.h>
#include <game.h>
const char *HI3_NAME = "BH3";
const char *HI3_ASSEMBLY_PATH = "BH3_Data/Native/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
{ 0x45221647, 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) {
msg_err_a("Invalid UnityPlayer.dll checksum: %x", crc);
}
buf->id = id;
buf->name = HI3_NAME;
buf->assembly_path = HI3_ASSEMBLY_PATH;
buf->tp6_section_name = HI3_TP6_SECTION_NAME;
buf->tvm_section_name = HI3_TVM_SECTION_NAME;
buf->unityplayer_callback = NULL;
}

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;
}

View File

@ -1,100 +0,0 @@
#include <utils.h>
#include <msg.h>
#include <main.h>
#include <game.h>
const char *HSR_NAME = "StarRail";
const char *HSR_ASSEMBLY_PATH = "GameAssembly.dll";
const char *HSR_TP6_SECTION_NAME = ".ace";
const char *HSR_TVM_SECTION_NAME = ".tvm0";
struct crc_id_pair {
uint32_t crc;
enum game_id id;
};
const struct crc_id_pair HSR_REGIONS[] = {
// It may be possible to get rid of region-specific data altogether in the future
{ 0x2df53005, GAME_HSR_OS }, // os v1.1.0
{ 0x3e644d26, GAME_HSR_CN } // cn v1.1.0
};
#define JUMP_SIZE (6 + sizeof(void*))
// Temporarily hardcoded offset
// v1.1.0, same for os and cn
#define WTSUD_PATCH_OFFSET 0x16430
char wtsud_original_bytes[JUMP_SIZE];
char *wtsud_patch_addr;
static void _wtsud_stub() {
// Recover original bytes
DWORD oldProtect;
VirtualProtect(wtsud_patch_addr, JUMP_SIZE, PAGE_EXECUTE_READWRITE, &oldProtect);
memcpy(wtsud_patch_addr, wtsud_original_bytes, JUMP_SIZE);
VirtualProtect(wtsud_patch_addr, JUMP_SIZE, oldProtect, &oldProtect);
unload_ctr_dec();
}
static void _unityplayer_callback(HMODULE unityModule) {
if (utils_env_enabled("SRFIX_DISABLE")) {
msg_info_a("Shared resources fix disabled. The game may not work");
return;
}
// Remove dependency on shared resources by patching WriteTextureStatisticUserData
unload_ctr_inc();
wtsud_patch_addr = ((char*)unityModule) + WTSUD_PATCH_OFFSET;
DWORD oldProtect;
VirtualProtect(wtsud_patch_addr, JUMP_SIZE, PAGE_EXECUTE_READWRITE, &oldProtect);
// Save original bytes
memcpy(wtsud_original_bytes, wtsud_patch_addr, JUMP_SIZE);
// Write jump
const char JUMP_INST[] = { 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00 }; // jmp [$ + 6]
memcpy(wtsud_patch_addr, JUMP_INST, sizeof(JUMP_INST));
// Write destination address
void *destAddr = &_wtsud_stub;
memcpy(wtsud_patch_addr + sizeof(JUMP_INST), &destAddr, sizeof(destAddr));
VirtualProtect(wtsud_patch_addr, JUMP_SIZE, oldProtect, &oldProtect);
}
void hsr_fill_data(struct game_data *buf) {
if (!utils_env_enabled("I_WANT_A_BAN")) {
msg_err_a("Using this tool with HSR is unsafe. Refer to the readme for more details");
} else {
msg_warn_a("Using this tool with HSR will most likely result in a ban. Please only use testing accounts");
}
uint32_t crc = utils_file_crc32c("UnityPlayer.dll");
enum game_id id = GAME_INVALID;
for (size_t i = 0; i < sizeof(HSR_REGIONS) / sizeof(struct crc_id_pair); i++) {
if (HSR_REGIONS[i].crc == crc) {
id = HSR_REGIONS[i].id;
}
}
if (id == GAME_INVALID) {
msg_err_a("Invalid UnityPlayer.dll checksum: %x", crc);
}
buf->id = id;
buf->name = HSR_NAME;
buf->assembly_path = HSR_ASSEMBLY_PATH;
buf->tp6_section_name = HSR_TP6_SECTION_NAME;
buf->tvm_section_name = HSR_TVM_SECTION_NAME;
buf->unityplayer_callback = &_unityplayer_callback;
}

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 "1.6.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[] = {
{ 0x5741ce50, HSR_OS }, // os v1.6.0
{ 0xce891f97, HSR_CN } // cn v1.6.0
};
#define JUMP_SIZE (6 + sizeof(void*))
// Temporarily hardcoded offset
// v1.6.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,14 @@
#include <windows.h> #include <windows.h>
#include <ntdll.h>
#include <ace.h> #include <ace.h>
#include <game.h> #include <game.h>
#include <tp6.h> #include <core.h>
#include <utils.h> #include <utils.h>
#include <msg.h>
#include <tx.h>
#define NTDLL_DYNAMIC_LINK_IMPL
#include <ntdll.h>
#include <main.h> #include <main.h>
@ -23,6 +27,48 @@ void unload_ctr_dec() {
} }
} }
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) { BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved) {
// Only listen to attach // Only listen to attach
if (reason != DLL_PROCESS_ATTACH) { if (reason != DLL_PROCESS_ATTACH) {
@ -32,25 +78,21 @@ BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved) {
this_module = instance; this_module = instance;
// Dynamically link functions from ntdll // Dynamically link functions from ntdll
ntdll_link(); _ntdll_link();
// Detect which game the user is trying to run // Detect which game the user is trying to run
struct game_data game; struct game_data game;
game_detect(&game); game_detect(&game);
// Create fake ACE driver files // Get required table file path
ace_fake_driver_files(); wchar_t txFile[MAX_PATH];
tx_table_file(&game, txFile);
// Load both ACE modules if (utils_path_exists(txFile)) {
HMODULE baseModule = ace_load_base_module(game.name); _run_game(&game, txFile);
ace_load_driver_module(); } else {
_run_tx(&game, txFile);
// ...magic }
tp6_setup_patcher(&game, baseModule);
// Load the UnityPlayer module and invoke the callback
HMODULE unityModule = LoadLibraryA("UnityPlayer.dll");
INVOKE_CALLBACK(game.unityplayer_callback, unityModule);
return TRUE; return TRUE;
} }

View File

@ -1,5 +1,6 @@
#include <windows.h> #include <windows.h>
#include <stdio.h> #include <stdio.h>
#include <config.h>
#include <msg.h> #include <msg.h>
@ -21,8 +22,8 @@
suffix; \ suffix; \
} }
const char *TITLE_A = "Jadeite Autopatcher"; const char *TITLE_A = "v" JADEITE_VERSION " Jadeite Autopatcher";
const wchar_t *TITLE_W = L"Jadeite Autopatcher"; const wchar_t *TITLE_W = L"v" JADEITE_VERSION " Jadeite Autopatcher";
// Error // Error
DEF_MSG_FN(msg_err_a, char, _vsnprintf, MessageBoxA, TITLE_A, MB_OK | MB_ICONERROR, exit(1)) DEF_MSG_FN(msg_err_a, char, _vsnprintf, MessageBoxA, TITLE_A, MB_OK | MB_ICONERROR, exit(1))

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

View File

@ -1,5 +0,0 @@
### 1.0.0
- First version
### 1.1.0
- HSR support

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

@ -5,10 +5,14 @@
#include <utils.h> #include <utils.h>
uint32_t utils_file_crc32c(const char *filePath) { int utils_path_exists(const wchar_t *filePath) {
HANDLE file = CreateFileA(filePath, FILE_READ_ACCESS, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 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) { if (!file) {
msg_err_a("Could not open file: %s", filePath); msg_err_w(L"Could not open file: %ls", filePath);
} }
LARGE_INTEGER fileSize; 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); HANDLE hMap = CreateFileMappingA(file, NULL, PAGE_READONLY, 0, 0, NULL);
char *map = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0); char *map = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
if (!map) { if (!map) {
msg_err_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); UnmapViewOfFile(map);
CloseHandle(hMap); CloseHandle(hMap);
@ -29,7 +33,43 @@ uint32_t utils_file_crc32c(const char *filePath) {
return crc; 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 utils_env_enabled(const char *env) {
char *envText = getenv(env); char *envText = getenv(env);
return envText && strcmp(envText, "") != 0; 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" 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 # Read project directory
proj_dir=`realpath "$1"` proj_dir=`realpath "$1"`
shift shift
# Read output file destinations # Read output file destinations and make sure they don't exist
resources_o=`realpath "$1"` if [ "x${gen_object}" = "x1" ]; then
shift resources_o=`realpath "$1"`
resources_h=`realpath "$1"` shift
shift
# Make sure that the header does not exist rm -f "${resources_h}"
rm -f "${resources_h}" fi
rm -f "${resources_o}" if [ "x${gen_header}" = "x1" ]; then
resources_h=`realpath "$1"`
shift
rm -f "${resources_o}"
fi
# Recomupte relative paths to parameters # Recomupte relative paths to parameters
idx=0 idx=0
@ -26,24 +45,28 @@ do
idx="$(("${idx}" + 1))" idx="$(("${idx}" + 1))"
done done
# Create the object file if [ "x${gen_object}" = "x1" ]; then
pushd "${proj_dir}" >> /dev/null # Create the object file
$linker -r -b binary -o "${resources_o}" "${resource_files[@]}" pushd "${proj_dir}" >> /dev/null
popd >> /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) if [ "x${gen_header}" = "x1" ]; then
echo "#include <stddef.h>" >> "${resources_h}" # Include stddef.h in the resources header (for size_t)
echo "#include <stddef.h>" >> "${resources_h}"
for resource in "${resource_files[@]}" for resource in "${resource_files[@]}"
do do
# Use relative path to the resource as the variable name # Use relative path to the resource as the variable name
var_name="_binary_${resource}" var_name="_binary_${resource}"
# Replace all non-alphanumeric characters with underscores # Replace all non-alphanumeric characters with underscores
var_name=`printf "${var_name}" | sed "s/[^a-zA-Z0-9]/_/g"` var_name=`printf "${var_name}" | sed "s/[^a-zA-Z0-9]/_/g"`
# Define externs in the header # Define externs in the header
echo "extern void *${var_name}_start;" >> "${resources_h}" echo "extern void *${var_name}_start;" >> "${resources_h}"
echo "extern void *${var_name}_size;" >> "${resources_h}" echo "extern void *${var_name}_size;" >> "${resources_h}"
echo "" >> "${resources_h}" echo "" >> "${resources_h}"
done done
fi

View File

@ -1,6 +1,6 @@
#pragma once #pragma once
#define EPFX "__JADEITE_" #define EPFX L"__JADEITE_"
#define ENV_EXE_PATH EPFX"TARGET_EXE_PATH" #define ENV_EXE_PATH EPFX"TARGET_EXE_PATH"
#define ENV_DLL_PATH EPFX"INJECT_DLL_PATH" #define ENV_DLL_PATH EPFX"INJECT_DLL_PATH"

View File

@ -2,4 +2,4 @@
#include <windows.h> #include <windows.h>
void inject(HANDLE process, const void *payload, size_t payloadSize, const char *dllPath); void inject(HANDLE process, const void *payload, size_t payloadSize, const wchar_t *dllPath);

View File

@ -17,14 +17,14 @@ exe_res_files = custom_target(
'launcher_p.[oh]', 'launcher_p.[oh]',
output: [ 'launcher_p.o', 'launcher_p.h' ], output: [ 'launcher_p.o', 'launcher_p.h' ],
input: [ launcher_payload_bin ], input: [ launcher_payload_bin ],
command: [ gen_res, './injector', '@OUTPUT0@', '@OUTPUT1@', '@INPUT@' ] command: [ gen_res, '--header', '--object', './injector', '@OUTPUT0@', '@OUTPUT1@', '@INPUT@' ]
) )
dll_res_files = custom_target( dll_res_files = custom_target(
'game_p.[oh]', 'game_p.[oh]',
output: [ 'game_p.o', 'game_p.h' ], output: [ 'game_p.o', 'game_p.h' ],
input: [ game_payload_bin ], input: [ game_payload_bin ],
command: [ gen_res, './injector', '@OUTPUT0@', '@OUTPUT1@', '@INPUT@' ] command: [ gen_res, '--header', '--object', './injector', '@OUTPUT0@', '@OUTPUT1@', '@INPUT@' ]
) )
# Main injector exe # Main injector exe
@ -34,7 +34,8 @@ executable(
'src/inject.c', 'src/inject.c',
exe_res_files, exe_res_files,
include_directories: include_dir, include_directories: include_dir,
name_prefix: '' name_prefix: '',
link_args: '-municode'
) )
# Dll that will be injected into the launcher # Dll that will be injected into the launcher
@ -44,5 +45,6 @@ shared_library(
'src/inject.c', 'src/inject.c',
dll_res_files, dll_res_files,
include_directories: include_dir, include_directories: include_dir,
name_prefix: '' name_prefix: '',
link_args: '-municode'
) )

View File

@ -5,6 +5,10 @@
#include <game_p.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) { BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, LPVOID reserved) {
// Only listen for attach // Only listen for attach
if (reason != DLL_PROCESS_ATTACH) { if (reason != DLL_PROCESS_ATTACH) {
@ -12,64 +16,106 @@ BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, LPVOID reserved) {
} }
// Get target EXE path // Get target EXE path
char *targetExe = getenv(ENV_EXE_PATH); wchar_t targetExe[MAX_PATH];
GetEnvironmentVariableW(ENV_EXE_PATH, targetExe, MAX_PATH);
// Get the path of the DLL to inject // Get the path of the DLL to inject
char *injectDll = getenv(ENV_DLL_PATH); wchar_t injectDll[MAX_PATH];
GetEnvironmentVariableW(ENV_DLL_PATH, injectDll, MAX_PATH);
// Get game commandline // Get game commandline
char *cmdline = getenv(ENV_PROC_CMD); wchar_t cmdline[8192];
GetEnvironmentVariableW(ENV_PROC_CMD, cmdline, sizeof(cmdline) / sizeof(wchar_t));
// Compute the working directory path // Compute the working directory path
char workdir[MAX_PATH]; wchar_t workdir[MAX_PATH];
strcpy(workdir, targetExe); wcscpy(workdir, targetExe);
*(strrchr(workdir, '\\')) = '\0'; *(wcsrchr(workdir, L'\\')) = L'\0';
// Start the game // SAFETY: verify that the injector is not inside the game directory
STARTUPINFO si; HMODULE kernel32 = GetModuleHandleA("kernel32.dll");
ZeroMemory(&si, sizeof(si)); wgufn_t wine_get_unix_file_name = (wgufn_t)GetProcAddress(kernel32, "wine_get_unix_file_name");
PROCESS_INFORMATION pi; if (wine_get_unix_file_name) {
si.cb = sizeof(si); char *unixInjectDll = wine_get_unix_file_name(injectDll);
ZeroMemory(&pi, sizeof(pi)); char *unixWorkdir = wine_get_unix_file_name(workdir);
if (!CreateProcessA( char *i = unixInjectDll, *w = unixWorkdir;
NULL, char startsWith = 0;
cmdline, while (*i && *w) {
NULL, startsWith = *i == *w;
NULL, if (!startsWith) break;
FALSE,
CREATE_SUSPENDED,
NULL,
workdir,
&si,
&pi
)) {
char message[64];
sprintf(message, "Failed to start game process: %ld", GetLastError());
MessageBoxA(NULL, message, "Jadeite Launcher Payload", MB_OK | MB_ICONERROR);
exit(1); 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);
} }
// Inject // Get restart flag file path
void *payloadStart = &_binary_game_p_o_p_game_p_bin_start; wchar_t restartFlagFile[MAX_PATH];
size_t payloadSize = (size_t)&_binary_game_p_o_p_game_p_bin_size; GetTempPathW(MAX_PATH, restartFlagFile);
inject(pi.hProcess, payloadStart, payloadSize, injectDll); wcscat(restartFlagFile, L"jadeite\\restart_flag");
// Optional: wait for user input before resuming (useful for debugging) do {
char *waitEnabled = getenv("WAIT_BEFORE_RESUME"); // Start the game
if (waitEnabled && strcmp(waitEnabled, "") != 0) { STARTUPINFOW si;
char message[64]; ZeroMemory(&si, sizeof(si));
sprintf(message, "PID: %ld. Press OK to continue", pi.dwProcessId); si.cb = sizeof(si);
MessageBoxA(NULL, message, "Jadeite Launcher Payload", MB_OK | MB_ICONINFORMATION);
}
// Resume the process PROCESS_INFORMATION pi;
ResumeThread(pi.hThread); ZeroMemory(&pi, sizeof(pi));
// The launcher process should now hang untill the game terminates if (!CreateProcessW(
WaitForSingleObject(pi.hProcess, INFINITE); 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; return TRUE;
} }

View File

@ -5,28 +5,28 @@
#include <launcher_p.h> #include <launcher_p.h>
const char LAUNCHER_INJECT_DLL[] = "launcher_payload.dll"; const wchar_t *LAUNCHER_INJECT_DLL = L"launcher_payload.dll";
const char GAME_INJECT_DLL[] = "game_payload.dll"; const wchar_t *GAME_INJECT_DLL = L"game_payload.dll";
#define SHIFT(argc, argv) argc--, argv++ #define SHIFT(argc, argv) argc--, argv++
int main(int argc, char **argv) { int wmain(int argc, wchar_t **argv) {
// Read arguments // Read arguments
char *gamePath = NULL; wchar_t *gamePath = NULL;
char *launcherPath = NULL; wchar_t *launcherPath = NULL;
// Skip executable // Skip executable
SHIFT(argc, argv); SHIFT(argc, argv);
switch (argc) { switch (argc) {
case 0: case 0:
printf("Usage: wine jadeite.exe [game path] <launcher path>\n"); wprintf(L"Usage: wine jadeite.exe [game path] <launcher path>\n");
return 0; return 0;
case 1: case 1:
gamePath = argv[0]; gamePath = argv[0];
SHIFT(argc, argv); SHIFT(argc, argv);
launcherPath = "--"; launcherPath = L"--";
break; break;
default: default:
@ -40,57 +40,57 @@ int main(int argc, char **argv) {
} }
// Default launcher path // Default launcher path
if (strcmp(launcherPath, "--") == 0) { if (wcscmp(launcherPath, L"--") == 0) {
printf("No launcher process specified! Using explorer.exe\n"); wprintf(L"No launcher process specified! Using explorer.exe\n");
launcherPath = "C:\\Windows\\explorer.exe"; launcherPath = L"C:\\Windows\\explorer.exe";
} }
// cd into the injector directory // cd into the injector directory
char injectorPath[MAX_PATH]; wchar_t injectorPath[MAX_PATH];
GetModuleFileNameA(GetModuleHandleA(NULL), injectorPath, sizeof(injectorPath)); GetModuleFileNameW(GetModuleHandleW(NULL), injectorPath, MAX_PATH);
*(strrchr(injectorPath, '\\')) = '\0'; *(wcsrchr(injectorPath, L'\\')) = L'\0';
SetCurrentDirectoryA(injectorPath); SetCurrentDirectoryW(injectorPath);
// Compute absolute paths // Compute absolute paths
char gameExePath[MAX_PATH]; wchar_t gameExePath[MAX_PATH];
GetFullPathNameA(gamePath, sizeof(gameExePath), gameExePath, NULL); GetFullPathNameW(gamePath, MAX_PATH, gameExePath, NULL);
char gamePayloadPath[MAX_PATH]; wchar_t gamePayloadPath[MAX_PATH];
GetFullPathNameA(GAME_INJECT_DLL, sizeof(gamePayloadPath), gamePayloadPath, NULL); GetFullPathNameW(GAME_INJECT_DLL, MAX_PATH, gamePayloadPath, NULL);
char launcherPayloadPath[MAX_PATH]; wchar_t launcherPayloadPath[MAX_PATH];
GetFullPathNameA(LAUNCHER_INJECT_DLL, sizeof(launcherPayloadPath), launcherPayloadPath, NULL); GetFullPathNameW(LAUNCHER_INJECT_DLL, MAX_PATH, launcherPayloadPath, NULL);
// Construct commandline for the game process // Construct commandline for the game process
char cmdline[8192]; wchar_t cmdline[8192];
sprintf(cmdline, "\"%s\"", gameExePath); wsprintfW(cmdline, L"\"%ls\"", gameExePath);
while (argc) { while (argc) {
char arg[8192]; wchar_t arg[8192];
sprintf(arg, " \"%s\"", argv[0]); wsprintfW(arg, L" \"%ls\"", argv[0]);
strcat(cmdline, arg); wcscat(cmdline, arg);
SHIFT(argc, argv); SHIFT(argc, argv);
} }
// Set envvars // Set envvars
SetEnvironmentVariableA(ENV_EXE_PATH, gameExePath); SetEnvironmentVariableW(ENV_EXE_PATH, gameExePath);
SetEnvironmentVariableA(ENV_DLL_PATH, gamePayloadPath); SetEnvironmentVariableW(ENV_DLL_PATH, gamePayloadPath);
SetEnvironmentVariableA(ENV_PROC_CMD, cmdline); SetEnvironmentVariableW(ENV_PROC_CMD, cmdline);
// Start the launcher // Start the launcher
printf("Starting '%s' via '%s'\n", gameExePath, launcherPath); wprintf(L"Starting '%ls' via '%ls'\n", gameExePath, launcherPath);
STARTUPINFO si; STARTUPINFOW si;
ZeroMemory(&si, sizeof(si)); ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
PROCESS_INFORMATION pi; PROCESS_INFORMATION pi;
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi)); ZeroMemory(&pi, sizeof(pi));
if (!CreateProcessA( if (!CreateProcessW(
launcherPath, launcherPath,
NULL, NULL,
NULL, NULL,
@ -102,11 +102,11 @@ int main(int argc, char **argv) {
&si, &si,
&pi &pi
)) { )) {
fprintf(stderr, "Could not start process! (%ld)\n", GetLastError()); fwprintf(stderr, L"Could not start process! (%ld)\n", GetLastError());
exit(1); exit(1);
} }
printf("Started launcher process (%ld)\n", pi.dwProcessId); wprintf(L"Started launcher process (%ld)\n", pi.dwProcessId);
// Inject // Inject
void *payloadStart = &_binary_launcher_p_o_p_launcher_p_bin_start; void *payloadStart = &_binary_launcher_p_o_p_launcher_p_bin_start;

View File

@ -1,5 +1,50 @@
BITS 64 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 main: ; Replacement entry point
push rsi push rsi
push rdi push rdi
@ -17,11 +62,19 @@ main: ; Replacement entry point
mov rcx, rsi ; kernel32.dll mov rcx, rsi ; kernel32.dll
lea rdx, [rel s_LoadLibraryA] lea rdx, [rel s_VirtualProtect]
call rdi ; rax = *LoadLibraryA 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] lea rcx, [rel dllPath]
call rax ; LoadLibraryA(dllPath) call rax ; LoadLibraryW(dllPath)
mov rcx, rsi ; kernel32.dll mov rcx, rsi ; kernel32.dll
@ -63,11 +116,66 @@ main: ; Replacement entry point
ret 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" %include "gpa.asm"
oldProtect: dd 0
; Strings ; Strings
s_LoadLibraryA: db "LoadLibraryA", 0 s_VirtualProtect: db "VirtualProtect", 0
s_LoadLibraryW: db "LoadLibraryW", 0
s_GetModuleHandleA: db "GetModuleHandleA", 0 s_GetModuleHandleA: db "GetModuleHandleA", 0
s_GetCommandLineW: db "GetCommandLineW", 0 s_GetCommandLineW: db "GetCommandLineW", 0
s_UnityPlayer.dll: db "UnityPlayer.dll", 0 s_UnityPlayer.dll: db "UnityPlayer.dll", 0

View File

@ -1,25 +1,32 @@
#include <inject.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) { static inline void write_protected_process_memory(HANDLE process, void *address, const void *buf, size_t size) {
DWORD oldProtect; DWORD oldProtect;
VirtualProtectEx(process, address, size, PAGE_EXECUTE_READWRITE, &oldProtect); VirtualProtectEx(process, address, size, PAGE_EXECUTE_READWRITE, &oldProtect);
size_t bytesWritten; WriteProcessMemory(process, address, buf, size, NULL);
WriteProcessMemory(process, address, buf, size, &bytesWritten);
VirtualProtectEx(process, address, size, oldProtect, &oldProtect); VirtualProtectEx(process, address, size, oldProtect, &oldProtect);
} }
void inject(HANDLE process, const void *payload, size_t payloadSize, const char *dllPath) { void inject(HANDLE process, const void *payload, size_t payloadSize, const wchar_t *dllPath) {
size_t _; // Contrary to the docs, {Write,Read}ProcessMemory likes to crash if the last arg is NULL
// 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, &_);
// Find the EXE header in the process // Find the EXE header in the process
char exeHeader[1024]; char exeHeader[1024];
IMAGE_DOS_HEADER *dosHeader = NULL; IMAGE_DOS_HEADER *dosHeader = NULL;
@ -28,7 +35,7 @@ void inject(HANDLE process, const void *payload, size_t payloadSize, const char
MEMORY_BASIC_INFORMATION memoryInfo; MEMORY_BASIC_INFORMATION memoryInfo;
char *currentAddress = 0x0; char *currentAddress = 0x0;
while (VirtualQueryEx(process, currentAddress, &memoryInfo, sizeof(memoryInfo))) { 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; dosHeader = (IMAGE_DOS_HEADER*)exeHeader;
@ -45,7 +52,7 @@ void inject(HANDLE process, const void *payload, size_t payloadSize, const char
} }
// Skip DLLs // Skip DLLs
if ((ntHeaders->FileHeader.Characteristics | IMAGE_FILE_DLL) == IMAGE_FILE_DLL) { if ((ntHeaders->FileHeader.Characteristics & IMAGE_FILE_DLL) == IMAGE_FILE_DLL) {
goto cont; goto cont;
} }
@ -64,25 +71,60 @@ void inject(HANDLE process, const void *payload, size_t payloadSize, const char
char *exe = (char*)memoryInfo.BaseAddress; 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 // Replace the entry point with a jump to the loader
char *entryPoint = exe + ntHeaders->OptionalHeader.AddressOfEntryPoint; 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, JUMP_INST, sizeof(JUMP_INST));
write_protected_process_memory(process, entryPoint + sizeof(JUMP_INST), &remoteAlloc, sizeof(remoteAlloc)); write_protected_process_memory(process, entryPoint + sizeof(JUMP_INST), &remoteAlloc, sizeof(remoteAlloc));
// Break the import table to prevent any dlls from being loaded // Break the import table to prevent any dlls from being loaded
// Step 1: break the first import descriptor // Step 1: break the first import descriptor
char *importDescriptors = exe + ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; 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; IMAGE_IMPORT_DESCRIPTOR firstDescriptor;
ZeroMemory(&firstDescriptor, sizeof(firstDescriptor)); ZeroMemory(&firstDescriptor, sizeof(firstDescriptor));
write_protected_process_memory(process, importDescriptors, &firstDescriptor, sizeof(firstDescriptor)); write_protected_process_memory(process, importDescriptors, &firstDescriptor, sizeof(firstDescriptor));
// Step 2: break the image data directory entry // Step 2: break the image data directory entry
size_t ddOffset = ((char*)&(ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size)) - exeHeader; char* ddAddr = ((char*)&(ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size)) - exeHeader + exe;
DWORD newSize = 0;
// Save the original value
rd.sizeFieldAddress = ddAddr;
ReadProcessMemory(process, rd.sizeFieldAddress, &rd.sizeFieldData, sizeof(rd.sizeFieldData), NULL);
write_protected_process_memory(process, exe + ddOffset, &newSize, sizeof(newSize)); // 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

@ -12,8 +12,8 @@ main: ; Replacement entry point
mov rcx, rsi ; kernel32.dll mov rcx, rsi ; kernel32.dll
lea rdx, [rel s_LoadLibraryA] lea rdx, [rel s_LoadLibraryW]
call rax ; rax = *LoadLibraryA call rax ; rax = *LoadLibraryW
lea rcx, [rel dllPath] lea rcx, [rel dllPath]
call rax ; LoadLibraryA(dllPath) call rax ; LoadLibraryA(dllPath)
@ -27,7 +27,7 @@ main: ; Replacement entry point
; Strings ; Strings
s_LoadLibraryA: db "LoadLibraryA", 0 s_LoadLibraryW: db "LoadLibraryW", 0
dllPath: dllPath:
; This will be filled out by the injector ; This will be filled out by the injector

View File

@ -1,4 +1,4 @@
project('jadeite', 'c', version: '1.1.5') project('jadeite', 'c', version: '3.1.0')
nasm = find_program('nasm') nasm = find_program('nasm')
gen_res = find_program('gen_resources.sh') gen_res = find_program('gen_resources.sh')

View File

@ -1,22 +1,42 @@
{ {
"jadeite": { "jadeite": {
"version": "1.1.5" "version": "3.1.0"
}, },
"games": { "games": {
"hi3rd": { "hi3rd": {
"global": { "global": {
"status": "verified", "status": "verified",
"version": "6.7.0" "version": "7.1.0"
},
"sea": {
"status": "verified",
"version": "7.1.0"
},
"china": {
"status": "verified",
"version": "7.2.0"
},
"taiwan": {
"status": "verified",
"version": "7.1.0"
},
"korea": {
"status": "verified",
"version": "7.1.0"
},
"japan": {
"status": "verified",
"version": "7.1.0"
} }
}, },
"hsr": { "hsr": {
"global": { "global": {
"status": "unsafe", "status": "verified",
"version": "1.1.0" "version": "1.6.0"
}, },
"china": { "china": {
"status": "unsafe", "status": "verified",
"version": "1.1.0" "version": "1.6.0"
} }
} }
} }