Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d22ded3d27 | |||
| 15c403b0d1 | |||
| 1cc16ecbd9 | |||
| ffe75e9de7 | |||
| 243e39d04b | |||
| daa5585527 | |||
| 8bf5aac0ec | |||
| 4c7dd302d5 | |||
| 63eed9d796 | |||
| 671f9ba246 | |||
| 868b4e27be | |||
| 56eefd83d7 | |||
| 3b2d33ad24 | |||
| ba6a054ec3 | |||
| 7693e63619 | |||
| 5d5d2117ec | |||
| ca65bbd70c | |||
| c17cf00409 | |||
| c80635fc71 | |||
| e9d2130105 | |||
| 0bfab4f682 | |||
| e0fcca3701 | |||
| 3b7cda6c5f | |||
| e066466339 | |||
| 612c2e74e6 | |||
| 4060fb5d4e | |||
| 54978e367c | |||
| e0d89875a0 | |||
| 89c6347315 | |||
| de15c00f2a | |||
| 694c734e67 | |||
| 0641006998 | |||
| 79cf7d20cc | |||
| 505d4b12dd | |||
| bb8d41c06d | |||
| b8aa6f968b | |||
| 9a3d623883 | |||
| 6b9f9b6d93 | |||
| 400729a3dc | |||
| 7eac309372 | |||
| 592ce62e6b | |||
| 4911f8d903 | |||
| 970561afb9 | |||
| dcb482ab8e | |||
| 5e2b015cc0 | |||
| 7beab899a6 | |||
| ac67488255 | |||
| 1375549216 | |||
| 19056bed0d |
10
README.md
10
README.md
@ -1,8 +1,8 @@
|
||||
### Games and regions
|
||||
- **3rd**: glb/sea/tw/kr/jp v6.7.0, cn v6.8.0
|
||||
- **SR**: os/cn v1.2.0 (potentially unsafe, but no bans were reported since v1.1.0)
|
||||
- **3rd**: glb/sea/tw/kr/jp **v6.8.0**, cn **v6.9.0+**
|
||||
- **SR**: os/cn **v1.3.0** (potentially unsafe, but no bans were reported since v1.1.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
|
||||
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`).
|
||||
@ -22,9 +22,9 @@ The anticheat the games use is fundamentally incompatible with Wine in multiple
|
||||
|
||||
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 `./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.
|
||||
|
||||
12
SERVERS.txt
12
SERVERS.txt
@ -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
36
block_analytics.sh
Normal 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
|
||||
1
build.sh
1
build.sh
@ -14,6 +14,7 @@ mkdir out
|
||||
cp ./build/injector/jadeite.exe ./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}
|
||||
|
||||
Binary file not shown.
@ -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();
|
||||
|
||||
1
game_payload/include/config.h.in
Normal file
1
game_payload/include/config.h.in
Normal file
@ -0,0 +1 @@
|
||||
#define JADEITE_VERSION "@version@"
|
||||
@ -4,4 +4,6 @@
|
||||
|
||||
#include <game.h>
|
||||
|
||||
void core_setup_patcher(struct game_data *game, HMODULE baseModule);
|
||||
void core_setup_patcher(struct game_data *game, HMODULE baseModule, wchar_t *txFile);
|
||||
|
||||
void *core_perform_tx(struct game_data *game, size_t *outLength);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -2,29 +2,14 @@
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
enum game_id {
|
||||
GAME_INVALID,
|
||||
|
||||
GAME_HI3_GLB,
|
||||
GAME_HI3_SEA,
|
||||
GAME_HI3_CN,
|
||||
GAME_HI3_TW,
|
||||
GAME_HI3_KR,
|
||||
GAME_HI3_JP,
|
||||
|
||||
GAME_HSR_OS,
|
||||
GAME_HSR_CN
|
||||
};
|
||||
|
||||
#define INVOKE_CALLBACK(callback, ...) if (callback) { callback(__VA_ARGS__); }
|
||||
|
||||
typedef void (*unityplayer_callback_t)(HMODULE unityModule);
|
||||
|
||||
struct game_data {
|
||||
enum game_id id; // Temporary
|
||||
const char *name;
|
||||
const char *assembly_name;
|
||||
const char *tp6_section_name; // Unused for now
|
||||
const char *base_module_name;
|
||||
const char *assembly_path;
|
||||
const char *txs_section_name;
|
||||
const char *tvm_section_name;
|
||||
|
||||
unityplayer_callback_t unityplayer_callback;
|
||||
|
||||
@ -1,4 +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();
|
||||
|
||||
@ -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);
|
||||
|
||||
3
game_payload/include/tx.h
Normal file
3
game_payload/include/tx.h
Normal file
@ -0,0 +1,3 @@
|
||||
#include <game.h>
|
||||
|
||||
void tx_table_file(struct game_data *game, wchar_t *buf);
|
||||
@ -2,6 +2,13 @@
|
||||
|
||||
#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);
|
||||
|
||||
@ -12,40 +12,15 @@ sources = [
|
||||
'src/hi3.c',
|
||||
'src/hsr.c',
|
||||
'src/utils.c',
|
||||
'src/msg.c'
|
||||
'src/msg.c',
|
||||
'src/tx.c'
|
||||
]
|
||||
resources = [
|
||||
'res/hi3/glb.dat',
|
||||
'res/hi3/sea.dat',
|
||||
'res/hi3/cn.dat',
|
||||
'res/hi3/tw.dat',
|
||||
'res/hi3/kr.dat',
|
||||
'res/hi3/jp.dat',
|
||||
|
||||
'res/hsr/os.dat',
|
||||
'res/hsr/cn.dat'
|
||||
]
|
||||
|
||||
# Generate resource files for ./res
|
||||
res_header = custom_target(
|
||||
'resources.h',
|
||||
output: 'resources.h',
|
||||
input: resources,
|
||||
command: [ gen_res, '--header', meson.current_source_dir(), '@OUTPUT0@', '@INPUT@' ]
|
||||
)
|
||||
res_object = custom_target(
|
||||
'resources.o',
|
||||
output: 'resources.o',
|
||||
input: resources,
|
||||
command: [ gen_res, '--object', meson.current_source_dir(), '@OUTPUT0@', '@INPUT@' ]
|
||||
)
|
||||
|
||||
if fs.exists('src/core.c')
|
||||
# Compile the real file first (dirty hack)
|
||||
core_fake_exe = executable(
|
||||
'core.o',
|
||||
'src/core.c',
|
||||
res_header,
|
||||
link_args: [ '-r' ], # Output an object file
|
||||
include_directories: include_dir
|
||||
)
|
||||
@ -70,12 +45,16 @@ else
|
||||
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_header,
|
||||
res_object,
|
||||
core_target,
|
||||
conf,
|
||||
objects: core_blob,
|
||||
include_directories: include_dir,
|
||||
name_prefix: ''
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -70,10 +70,9 @@ static HMODULE _load_module_patched(wchar_t *path) {
|
||||
return module;
|
||||
}
|
||||
|
||||
HMODULE ace_load_base_module(const char *exeName) {
|
||||
HMODULE ace_load_base_module(struct game_data *game) {
|
||||
wchar_t baseModuleName[MAX_PATH];
|
||||
swprintf(baseModuleName, MAX_PATH, L"%sBase.dll", exeName);
|
||||
wcslwr(baseModuleName);
|
||||
MultiByteToWideChar(CP_UTF8, 0, game->base_module_name, strlen(game->base_module_name) + 1, baseModuleName, MAX_PATH);
|
||||
|
||||
return _load_module_patched(baseModuleName);
|
||||
}
|
||||
|
||||
@ -16,3 +16,16 @@
|
||||
### 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
|
||||
|
||||
@ -1,32 +1,39 @@
|
||||
#include <msg.h>
|
||||
#include <utils.h>
|
||||
|
||||
#include <game.h>
|
||||
|
||||
typedef void (*fill_fn)(struct game_data *buf);
|
||||
|
||||
struct name_fn_pair {
|
||||
const char *name;
|
||||
const wchar_t *name;
|
||||
fill_fn fill;
|
||||
};
|
||||
|
||||
const struct name_fn_pair GAMES[] = {
|
||||
{ "bh3.exe", &hi3_fill_data },
|
||||
{ "starrail.exe", &hsr_fill_data }
|
||||
{ L"BH3", &hi3_fill_data },
|
||||
{ L"StarRail", &hsr_fill_data }
|
||||
};
|
||||
|
||||
void game_detect(struct game_data *buf) {
|
||||
char exePath[MAX_PATH];
|
||||
GetModuleFileNameA(NULL, exePath, MAX_PATH);
|
||||
wchar_t exePath[MAX_PATH];
|
||||
GetModuleFileNameW(NULL, exePath, MAX_PATH);
|
||||
|
||||
char *exeName = strrchr(exePath, '\\') + 1;
|
||||
strlwr(exeName);
|
||||
// Leave only the basename
|
||||
wchar_t *exeName = wcsrchr(exePath, L'\\') + 1;
|
||||
|
||||
for (size_t i = 0; i < sizeof(GAMES) / sizeof(struct name_fn_pair); i++) {
|
||||
if (strcmp(exeName, GAMES[i].name) == 0) {
|
||||
// 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_a("Unknown game: %s", exeName);
|
||||
msg_err_w(L"Unknown game: %ls", exeName);
|
||||
}
|
||||
|
||||
@ -3,45 +3,28 @@
|
||||
|
||||
#include <game.h>
|
||||
|
||||
const char *HI3_NAME = "BH3";
|
||||
const char *HI3_ASSEMBLY_NAME = "UserAssembly.dll";
|
||||
const char *HI3_TP6_SECTION_NAME = ".bh3";
|
||||
const char *HI3_TXS_SECTION_NAME_OLD = ".bh3";
|
||||
const char *HI3_TXS_SECTION_NAME_NEW = ".ace";
|
||||
|
||||
const char *HI3_BASE_MODULE_NAME = "BH3Base.dll";
|
||||
const char *HI3_ASSEMBLY_PATH = "BH3_Data\\Native\\UserAssembly.dll";
|
||||
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[] = {
|
||||
// It may be possible to get rid of region-specific data altogether in the future
|
||||
|
||||
{ 0x45221647, GAME_HI3_GLB }, // glb v6.7.0
|
||||
{ 0x32f97d39, GAME_HI3_SEA }, // sea v6.7.0
|
||||
{ 0x2efd9099, GAME_HI3_CN }, // cn v6.8.0
|
||||
{ 0x4a47e6c8, GAME_HI3_TW }, // tw v6.7.0
|
||||
{ 0x03b67cb4, GAME_HI3_KR }, // kr v6.7.0
|
||||
{ 0x449ff855, GAME_HI3_JP } // jp v6.7.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;
|
||||
}
|
||||
// !!! TEMPORARY WORKAROUND FOR HI3 6.8.0 -> 6.9.0
|
||||
const uint32_t NEW_CHECKSUM = 0x885b4c63;
|
||||
|
||||
uint32_t crc = utils_file_crc32c(L"UnityPlayer.dll");
|
||||
|
||||
if (crc == NEW_CHECKSUM) {
|
||||
buf->txs_section_name = HI3_TXS_SECTION_NAME_NEW;
|
||||
} else {
|
||||
buf->txs_section_name = HI3_TXS_SECTION_NAME_OLD;
|
||||
}
|
||||
|
||||
if (id == GAME_INVALID) {
|
||||
msg_err_a("Invalid UnityPlayer.dll checksum: %x", crc);
|
||||
}
|
||||
|
||||
buf->id = id;
|
||||
buf->name = HI3_NAME;
|
||||
buf->assembly_name = HI3_ASSEMBLY_NAME;
|
||||
buf->tp6_section_name = HI3_TP6_SECTION_NAME;
|
||||
|
||||
buf->base_module_name = HI3_BASE_MODULE_NAME;
|
||||
buf->assembly_path = HI3_ASSEMBLY_PATH;
|
||||
buf->tvm_section_name = HI3_TVM_SECTION_NAME;
|
||||
|
||||
buf->unityplayer_callback = NULL;
|
||||
|
||||
@ -4,27 +4,33 @@
|
||||
|
||||
#include <game.h>
|
||||
|
||||
const char *HSR_NAME = "StarRail";
|
||||
const char *HSR_ASSEMBLY_NAME = "GameAssembly.dll";
|
||||
const char *HSR_TP6_SECTION_NAME = ".ace";
|
||||
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";
|
||||
|
||||
struct crc_id_pair {
|
||||
uint32_t crc;
|
||||
enum game_id id;
|
||||
#define HSR_VERSION "1.3.0"
|
||||
|
||||
enum hsr_region {
|
||||
HSR_INVALID,
|
||||
HSR_OS,
|
||||
HSR_CN
|
||||
};
|
||||
|
||||
const struct crc_id_pair HSR_REGIONS[] = {
|
||||
// It may be possible to get rid of region-specific data altogether in the future
|
||||
struct crc_region_pair {
|
||||
uint32_t crc;
|
||||
enum hsr_region id;
|
||||
};
|
||||
|
||||
{ 0x9eb3084e, GAME_HSR_OS }, // os v1.2.0
|
||||
{ 0x14be07e9, GAME_HSR_CN } // cn v1.2.0
|
||||
const struct crc_region_pair HSR_REGIONS[] = {
|
||||
{ 0x748c8f9c, HSR_OS }, // os v1.3.0
|
||||
{ 0x76e85a40, HSR_CN } // cn v1.3.0
|
||||
};
|
||||
|
||||
#define JUMP_SIZE (6 + sizeof(void*))
|
||||
|
||||
// Temporarily hardcoded offset
|
||||
// v1.2.0, same for os and cn
|
||||
// v1.3.0, same for os and cn
|
||||
#define WTSUD_PATCH_OFFSET 0x16430
|
||||
|
||||
char wtsud_original_bytes[JUMP_SIZE];
|
||||
@ -71,23 +77,23 @@ static void _unityplayer_callback(HMODULE unityModule) {
|
||||
}
|
||||
|
||||
void hsr_fill_data(struct game_data *buf) {
|
||||
uint32_t crc = utils_file_crc32c("UnityPlayer.dll");
|
||||
uint32_t crc = utils_file_crc32c(L"UnityPlayer.dll");
|
||||
|
||||
enum game_id id = GAME_INVALID;
|
||||
for (size_t i = 0; i < sizeof(HSR_REGIONS) / sizeof(struct crc_id_pair); i++) {
|
||||
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 == GAME_INVALID) {
|
||||
msg_err_a("Invalid UnityPlayer.dll checksum: %x", crc);
|
||||
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);
|
||||
}
|
||||
|
||||
buf->id = id;
|
||||
buf->name = HSR_NAME;
|
||||
buf->assembly_name = HSR_ASSEMBLY_NAME;
|
||||
buf->tp6_section_name = HSR_TP6_SECTION_NAME;
|
||||
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;
|
||||
|
||||
buf->unityplayer_callback = &_unityplayer_callback;
|
||||
|
||||
@ -5,6 +5,8 @@
|
||||
#include <game.h>
|
||||
#include <core.h>
|
||||
#include <utils.h>
|
||||
#include <msg.h>
|
||||
#include <tx.h>
|
||||
|
||||
#include <main.h>
|
||||
|
||||
@ -23,6 +25,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) {
|
||||
// Only listen to attach
|
||||
if (reason != DLL_PROCESS_ATTACH) {
|
||||
@ -38,19 +82,15 @@ BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved) {
|
||||
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
|
||||
core_setup_patcher(&game, baseModule);
|
||||
|
||||
// Load the UnityPlayer module and invoke the callback
|
||||
HMODULE unityModule = LoadLibraryA("UnityPlayer.dll");
|
||||
INVOKE_CALLBACK(game.unityplayer_callback, unityModule);
|
||||
if (utils_path_exists(txFile)) {
|
||||
_run_game(&game, txFile);
|
||||
} else {
|
||||
_run_tx(&game, txFile);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <config.h>
|
||||
|
||||
#include <msg.h>
|
||||
|
||||
@ -21,8 +22,8 @@
|
||||
suffix; \
|
||||
}
|
||||
|
||||
const char *TITLE_A = "Jadeite Autopatcher";
|
||||
const wchar_t *TITLE_W = L"Jadeite Autopatcher";
|
||||
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))
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
#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);
|
||||
@ -9,17 +9,15 @@ void pe_find_section(HMODULE module, const char *section, MEMORY_BASIC_INFORMATI
|
||||
WORD sectionCount = ntHeaders->FileHeader.NumberOfSections;
|
||||
IMAGE_SECTION_HEADER* sectionHeader = (IMAGE_SECTION_HEADER*)(ntHeaders + 1);
|
||||
|
||||
void* targetAddress = 0x0;
|
||||
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) {
|
||||
|
||||
44
game_payload/src/tx.c
Normal file
44
game_payload/src/tx.c
Normal 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);
|
||||
}
|
||||
@ -5,10 +5,14 @@
|
||||
|
||||
#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) {
|
||||
msg_err_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) {
|
||||
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);
|
||||
CloseHandle(hMap);
|
||||
@ -29,6 +33,33 @@ 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;
|
||||
|
||||
@ -61,51 +61,61 @@ BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, LPVOID reserved) {
|
||||
MessageBoxW(NULL, L"Could not find wine_get_unix_file_name! Wine version too old?", J_MB_TITLE, MB_OK | MB_ICONWARNING);
|
||||
}
|
||||
|
||||
// Start the game
|
||||
STARTUPINFOW si;
|
||||
ZeroMemory(&si, sizeof(si));
|
||||
// Get restart flag file path
|
||||
wchar_t restartFlagFile[MAX_PATH];
|
||||
GetTempPathW(MAX_PATH, restartFlagFile);
|
||||
wcscat(restartFlagFile, L"jadeite\\restart_flag");
|
||||
|
||||
PROCESS_INFORMATION pi;
|
||||
si.cb = sizeof(si);
|
||||
ZeroMemory(&pi, sizeof(pi));
|
||||
do {
|
||||
// Start the game
|
||||
STARTUPINFOW si;
|
||||
ZeroMemory(&si, sizeof(si));
|
||||
si.cb = sizeof(si);
|
||||
|
||||
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", GetLastError());
|
||||
MessageBoxW(NULL, message, J_MB_TITLE, MB_OK | MB_ICONERROR);
|
||||
PROCESS_INFORMATION pi;
|
||||
ZeroMemory(&pi, sizeof(pi));
|
||||
|
||||
exit(1);
|
||||
}
|
||||
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);
|
||||
|
||||
// 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);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
// 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);
|
||||
|
||||
// Resume the process
|
||||
ResumeThread(pi.hThread);
|
||||
// Remove the restart flag file
|
||||
DeleteFileW(restartFlagFile);
|
||||
|
||||
// The launcher process should now hang untill the game terminates
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
// 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;
|
||||
}
|
||||
|
||||
@ -85,9 +85,9 @@ int wmain(int argc, wchar_t **argv) {
|
||||
|
||||
STARTUPINFOW si;
|
||||
ZeroMemory(&si, sizeof(si));
|
||||
si.cb = sizeof(si);
|
||||
|
||||
PROCESS_INFORMATION pi;
|
||||
si.cb = sizeof(si);
|
||||
ZeroMemory(&pi, sizeof(pi));
|
||||
|
||||
if (!CreateProcessW(
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
project('jadeite', 'c', version: '2.0.0')
|
||||
project('jadeite', 'c', version: '3.0.7')
|
||||
|
||||
nasm = find_program('nasm')
|
||||
gen_res = find_program('gen_resources.sh')
|
||||
|
||||
@ -1,42 +1,42 @@
|
||||
{
|
||||
"jadeite": {
|
||||
"version": "2.0.0"
|
||||
"version": "3.0.7"
|
||||
},
|
||||
"games": {
|
||||
"hi3rd": {
|
||||
"global": {
|
||||
"status": "verified",
|
||||
"version": "6.7.0"
|
||||
"version": "6.8.0"
|
||||
},
|
||||
"sea": {
|
||||
"status": "verified",
|
||||
"version": "6.7.0"
|
||||
"version": "6.8.0"
|
||||
},
|
||||
"china": {
|
||||
"status": "verified",
|
||||
"version": "6.8.0"
|
||||
"version": "6.9.0"
|
||||
},
|
||||
"taiwan": {
|
||||
"status": "verified",
|
||||
"version": "6.7.0"
|
||||
"version": "6.8.0"
|
||||
},
|
||||
"korea": {
|
||||
"status": "verified",
|
||||
"version": "6.7.0"
|
||||
"version": "6.8.0"
|
||||
},
|
||||
"japan": {
|
||||
"status": "verified",
|
||||
"version": "6.7.0"
|
||||
"version": "6.8.0"
|
||||
}
|
||||
},
|
||||
"hsr": {
|
||||
"global": {
|
||||
"status": "verified",
|
||||
"version": "1.2.0"
|
||||
"version": "1.3.0"
|
||||
},
|
||||
"china": {
|
||||
"status": "verified",
|
||||
"version": "1.2.0"
|
||||
"version": "1.3.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user