39 Commits

Author SHA1 Message Date
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
34 changed files with 383 additions and 235 deletions

View File

@ -1,8 +1,8 @@
### Games and regions ### Games and regions
- **3rd**: glb v6.7.0 - **3rd**: glb/sea/cn/tw/kr/jp **v6.8.0+**
- **SR**: os/cn v1.2.0 (potentially unsafe, but no bans were reported since v1.1.0) - **SR**: os/cn **v1.2.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 of 3rd to work immediately after release with the same jadeite binary.** However, that is not the case for SR: you will have to update your jadeite binary to run newer versions.
### Information ### 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`). 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: 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. 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/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}

Binary file not shown.

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

@ -4,4 +4,6 @@
#include <game.h> #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. */ /* 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,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

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

View File

@ -2,6 +2,11 @@
#include <stdint.h> #include <stdint.h>
uint32_t utils_file_crc32c(const char *filePath); int utils_path_exists(const wchar_t *filePath);
uint32_t utils_file_crc32c(const wchar_t *filePath);
void utils_create_dir_recursively(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);

View File

@ -12,38 +12,15 @@ sources = [
'src/hi3.c', 'src/hi3.c',
'src/hsr.c', 'src/hsr.c',
'src/utils.c', 'src/utils.c',
'src/msg.c' 'src/msg.c',
'src/tx.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
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') if fs.exists('src/core.c')
# Compile the real file first (dirty hack) # Compile the real file first (dirty hack)
core_fake_exe = executable( core_fake_exe = executable(
'core.o', 'core.o',
'src/core.c', 'src/core.c',
res_header,
link_args: [ '-r' ], # Output an object file link_args: [ '-r' ], # Output an object file
include_directories: include_dir include_directories: include_dir
) )
@ -68,12 +45,16 @@ else
core_blob = [ 'blob/core.o' ] core_blob = [ 'blob/core.o' ]
endif 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_header,
res_object,
core_target, core_target,
conf,
objects: core_blob, objects: core_blob,
include_directories: include_dir, include_directories: include_dir,
name_prefix: '' name_prefix: ''

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -9,83 +9,74 @@ 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
};
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);
}
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;
} }

View File

@ -12,3 +12,17 @@
### 1.1.11 ### 1.1.11
- Fixed an additional issue introduced in 1.1.9 - 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
### branch/master
- Fixed multiple error messageboxes showing invalid characters
- Added handling for more error conditions

View File

@ -3,41 +3,16 @@
#include <game.h> #include <game.h>
const char *HI3_NAME = "BH3"; const char *HI3_BASE_MODULE_NAME = "BH3Base.dll";
const char *HI3_ASSEMBLY_PATH = "BH3_Data/Native/UserAssembly.dll"; const char *HI3_ASSEMBLY_PATH = "BH3_Data\\Native\\UserAssembly.dll";
const char *HI3_TP6_SECTION_NAME = ".bh3"; const char *HI3_TXS_SECTION_NAME = ".bh3";
const char *HI3_TVM_SECTION_NAME = ".tvm0"; 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.7.0
};
void hi3_fill_data(struct game_data *buf) { void hi3_fill_data(struct game_data *buf) {
uint32_t crc = utils_file_crc32c("UnityPlayer.dll"); buf->base_module_name = HI3_BASE_MODULE_NAME;
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->assembly_path = HI3_ASSEMBLY_PATH;
buf->tp6_section_name = HI3_TP6_SECTION_NAME; buf->txs_section_name = HI3_TXS_SECTION_NAME;
buf->tvm_section_name = HI3_TVM_SECTION_NAME; buf->tvm_section_name = HI3_TVM_SECTION_NAME;
buf->unityplayer_callback = NULL; buf->unityplayer_callback = NULL;

View File

@ -4,21 +4,25 @@
#include <game.h> #include <game.h>
const char *HSR_NAME = "StarRail"; const char *HSR_BASE_MODULE_NAME = "StarRailBase.dll";
const char *HSR_ASSEMBLY_PATH = "GameAssembly.dll"; const char *HSR_ASSEMBLY_PATH = "GameAssembly.dll";
const char *HSR_TP6_SECTION_NAME = ".ace"; const char *HSR_TXS_SECTION_NAME = ".ace";
const char *HSR_TVM_SECTION_NAME = ".tvm0"; const char *HSR_TVM_SECTION_NAME = ".tvm0";
struct crc_id_pair { enum hsr_region {
uint32_t crc; HSR_INVALID,
enum game_id id; HSR_OS,
HSR_CN
}; };
const struct crc_id_pair HSR_REGIONS[] = { struct crc_region_pair {
// It may be possible to get rid of region-specific data altogether in the future uint32_t crc;
enum hsr_region id;
};
{ 0x9eb3084e, GAME_HSR_OS }, // os v1.2.0 const struct crc_region_pair HSR_REGIONS[] = {
{ 0x14be07e9, GAME_HSR_CN } // cn v1.2.0 { 0x9eb3084e, HSR_OS }, // os v1.2.0
{ 0x14be07e9, HSR_CN } // cn v1.2.0
}; };
#define JUMP_SIZE (6 + sizeof(void*)) #define JUMP_SIZE (6 + sizeof(void*))
@ -71,23 +75,22 @@ static void _unityplayer_callback(HMODULE unityModule) {
} }
void hsr_fill_data(struct game_data *buf) { 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; enum hsr_region id = HSR_INVALID;
for (size_t i = 0; i < sizeof(HSR_REGIONS) / sizeof(struct crc_id_pair); i++) { for (size_t i = 0; i < sizeof(HSR_REGIONS) / sizeof(struct crc_region_pair); i++) {
if (HSR_REGIONS[i].crc == crc) { if (HSR_REGIONS[i].crc == crc) {
id = HSR_REGIONS[i].id; id = HSR_REGIONS[i].id;
} }
} }
if (id == GAME_INVALID) { if (id == HSR_INVALID) {
msg_err_a("Invalid UnityPlayer.dll checksum: %x", crc); msg_err_a("Invalid UnityPlayer.dll checksum: %x", crc);
} }
buf->id = id; buf->base_module_name = HSR_BASE_MODULE_NAME;
buf->name = HSR_NAME;
buf->assembly_path = HSR_ASSEMBLY_PATH; buf->assembly_path = HSR_ASSEMBLY_PATH;
buf->tp6_section_name = HSR_TP6_SECTION_NAME; buf->txs_section_name = HSR_TXS_SECTION_NAME;
buf->tvm_section_name = HSR_TVM_SECTION_NAME; buf->tvm_section_name = HSR_TVM_SECTION_NAME;
buf->unityplayer_callback = &_unityplayer_callback; buf->unityplayer_callback = &_unityplayer_callback;

View File

@ -5,6 +5,8 @@
#include <game.h> #include <game.h>
#include <core.h> #include <core.h>
#include <utils.h> #include <utils.h>
#include <msg.h>
#include <tx.h>
#include <main.h> #include <main.h>
@ -23,6 +25,54 @@ 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) {
// Load unpatched base module
HMODULE baseModule = LoadLibraryA(game->base_module_name);
if (!baseModule) {
msg_err_a("Failed to load base module: %s", game->base_module_name);
}
// ...more magic
size_t tableSize;
void *table = core_perform_tx(game, &tableSize);
// Save to file
utils_create_dir_recursively(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) {
@ -38,19 +88,15 @@ BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved) {
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 }
core_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,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) {

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

@ -0,0 +1,40 @@
#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);
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,6 +33,36 @@ uint32_t utils_file_crc32c(const char *filePath) {
return crc; return crc;
} }
// https://stackoverflow.com/a/16719260
void utils_create_dir_recursively(const wchar_t *path) {
wchar_t dir[MAX_PATH];
ZeroMemory(dir, MAX_PATH * sizeof(wchar_t));
wchar_t *end = wcschr(path, L'\\');
while(end != 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);
}
end = wcschr(++end, L'\\');
}
}
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 && *envText; 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); 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 // Get restart flag file path
STARTUPINFOW si; wchar_t restartFlagFile[MAX_PATH];
ZeroMemory(&si, sizeof(si)); GetTempPathW(MAX_PATH, restartFlagFile);
wcscat(restartFlagFile, L"jadeite\\restart_flag");
PROCESS_INFORMATION pi; do {
si.cb = sizeof(si); // Start the game
ZeroMemory(&pi, sizeof(pi)); STARTUPINFOW si;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
if (!CreateProcessW( PROCESS_INFORMATION pi;
NULL, ZeroMemory(&pi, sizeof(pi));
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);
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", GetLastError());
MessageBoxW(NULL, message, J_MB_TITLE, MB_OK | MB_ICONERROR);
// Inject exit(1);
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);
// Optional: wait for user input before resuming (useful for debugging) // Inject
char *waitEnabled = getenv("WAIT_BEFORE_RESUME"); void *payloadStart = &_binary_game_p_o_p_game_p_bin_start;
if (waitEnabled && *waitEnabled) { size_t payloadSize = (size_t)&_binary_game_p_o_p_game_p_bin_size;
wchar_t message[64]; inject(pi.hProcess, payloadStart, payloadSize, injectDll);
wsprintfW(message, L"PID: %ld. Press OK to continue", pi.dwProcessId);
MessageBoxW(NULL, message, J_MB_TITLE, MB_OK | MB_ICONINFORMATION);
}
// Resume the process // Remove the restart flag file
ResumeThread(pi.hThread); DeleteFileW(restartFlagFile);
// The launcher process should now hang untill the game terminates // Optional: wait for user input before resuming (useful for debugging)
WaitForSingleObject(pi.hProcess, INFINITE); 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

@ -85,9 +85,9 @@ int wmain(int argc, wchar_t **argv) {
STARTUPINFOW 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 (!CreateProcessW( if (!CreateProcessW(

View File

@ -1,4 +1,4 @@
project('jadeite', 'c', version: '1.1.13') project('jadeite', 'c', version: '3.0.3')
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,12 +1,32 @@
{ {
"jadeite": { "jadeite": {
"version": "1.1.13" "version": "3.0.3"
}, },
"games": { "games": {
"hi3rd": { "hi3rd": {
"global": { "global": {
"status": "verified", "status": "verified",
"version": "6.7.0" "version": "6.8.0"
},
"sea": {
"status": "verified",
"version": "6.8.0"
},
"china": {
"status": "verified",
"version": "6.8.0"
},
"taiwan": {
"status": "verified",
"version": "6.8.0"
},
"korea": {
"status": "verified",
"version": "6.8.0"
},
"japan": {
"status": "verified",
"version": "6.8.0"
} }
}, },
"hsr": { "hsr": {