49 Commits

Author SHA1 Message Date
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
37 changed files with 355 additions and 212 deletions

View File

@ -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.

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

@ -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.

View File

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

View File

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

View File

@ -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);

View File

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

View File

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

View File

@ -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();

View File

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

View File

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

View File

@ -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);

View File

@ -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.

View File

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

View File

@ -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

View File

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

View File

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

View File

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

View File

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

View File

@ -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))

View File

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

View File

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

View File

@ -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(

View File

@ -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')

View File

@ -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"
}
}
}