Add configurable URL hooks

Signed-off-by: Sean Du <do4suki@gmail.com>
This commit is contained in:
2026-06-20 08:12:06 +08:00
parent 58aa9be073
commit baa0f6e7e7
11 changed files with 233 additions and 3 deletions
+2
View File
@@ -11,6 +11,7 @@ add_library(version SHARED
src/proxy.cpp src/proxy.cpp
src/hook.cpp src/hook.cpp
src/log.cpp src/log.cpp
src/config.cpp
src/il2cpp_symbols.cpp src/il2cpp_symbols.cpp
src/version.rc src/version.rc
src/version.def src/version.def
@@ -25,6 +26,7 @@ target_include_directories(version PRIVATE
deps/minhook/include deps/minhook/include
deps/minhook/src deps/minhook/src
deps/minhook/src/hde deps/minhook/src/hde
deps/rapidjson/include
) )
target_compile_definitions(version PRIVATE target_compile_definitions(version PRIVATE
+2
View File
@@ -8,6 +8,7 @@ CXX := x86_64-w64-mingw32-g++
WINDRES := x86_64-w64-mingw32-windres WINDRES := x86_64-w64-mingw32-windres
INCLUDES := -Isrc -Ideps/minhook/include -Ideps/minhook/src -Ideps/minhook/src/hde INCLUDES := -Isrc -Ideps/minhook/include -Ideps/minhook/src -Ideps/minhook/src/hde
INCLUDES += -Ideps/rapidjson/include
DEFINES := -DNOMINMAX -DWIN32_LEAN_AND_MEAN DEFINES := -DNOMINMAX -DWIN32_LEAN_AND_MEAN
CFLAGS := -std=gnu11 $(DEFINES) $(INCLUDES) CFLAGS := -std=gnu11 $(DEFINES) $(INCLUDES)
CXXFLAGS := -std=gnu++20 -fno-exceptions -fno-rtti $(DEFINES) $(INCLUDES) CXXFLAGS := -std=gnu++20 -fno-exceptions -fno-rtti $(DEFINES) $(INCLUDES)
@@ -34,6 +35,7 @@ CXX_SRCS := \
src/proxy.cpp \ src/proxy.cpp \
src/hook.cpp \ src/hook.cpp \
src/log.cpp \ src/log.cpp \
src/config.cpp \
src/il2cpp_symbols.cpp src/il2cpp_symbols.cpp
RC_SRCS := src/version.rc RC_SRCS := src/version.rc
+17 -2
View File
@@ -6,6 +6,7 @@
- 转发 `UnityPlayer.dll` 实际导入的少量 `version.dll` 导出函数 - 转发 `UnityPlayer.dll` 实际导入的少量 `version.dll` 导出函数
- 等待 IL2CPP 运行时初始化稳定 - 等待 IL2CPP 运行时初始化稳定
- 运行时 hook `Stage.NetworkUtil.GetSchemeType()`,强制返回 `0` - 运行时 hook `Stage.NetworkUtil.GetSchemeType()`,强制返回 `0`
- 支持通过 `config.json` 覆盖 `api_url``asset_url`
**目录结构** **目录结构**
- `src/`:代理、日志、IL2CPP 符号解析、hook 逻辑 - `src/`:代理、日志、IL2CPP 符号解析、hook 逻辑
@@ -16,6 +17,7 @@
- `GameAssembly.dll` 出现时,IL2CPP runtime 可能还不能安全访问,因此 hook 线程会额外等待一小段时间 - `GameAssembly.dll` 出现时,IL2CPP runtime 可能还不能安全访问,因此 hook 线程会额外等待一小段时间
- 运行时日志会写到游戏目录下的 `cgss-http-hook.log` - 运行时日志会写到游戏目录下的 `cgss-http-hook.log`
- 生成的 DLL 包含 Windows 版本资源,定义在 `src/version.rc` - 生成的 DLL 包含 Windows 版本资源,定义在 `src/version.rc`
- `config.json` 中的 URL 会自动规范化:若写了 `http://``https://` 会自动去掉,若末尾缺少 `/` 会自动补上
**构建** **构建**
```sh ```sh
@@ -28,7 +30,20 @@ cmake --build build
**输出** **输出**
- `build/version.dll` - `build/version.dll`
**config.json**
```json
{
"api_url": "apis.game.starlight-stage.jp/",
"asset_url": "asset-starlight-stage.akamaized.net/"
}
```
字段说明:
- `api_url`:API 主机名,按原始字段格式填写,不带 scheme
- `asset_url`:资源主机名,按原始字段格式填写,不带 scheme
**使用方法** **使用方法**
1.`build/version.dll` 复制到 `imascgstage.exe` 同目录 1.`build/version.dll` 复制到 `imascgstage.exe` 同目录
2. 启动游戏 2. 如需覆盖地址,在同目录放置 `config.json`
3. 如果 hook 未生效,查看 `cgss-http-hook.log` 3. 启动游戏
4. 如果 hook 未生效,查看 `cgss-http-hook.log`
+4
View File
@@ -0,0 +1,4 @@
{
"api_url": "apis.game.starlight-stage.jp/",
"asset_url": "asset-starlight-stage.akamaized.net/"
}
+105
View File
@@ -0,0 +1,105 @@
#include "stdinclude.hpp"
#include "config.hpp"
namespace config {
namespace {
OverrideUrls g_urls;
bool g_loaded = false;
std::string get_config_path() {
char path[MAX_PATH] = {};
DWORD len = GetModuleFileNameA(nullptr, path, MAX_PATH);
if (len == 0 || len >= MAX_PATH) {
return {};
}
char* last_sep = path;
for (char* p = path; *p != '\0'; ++p) {
if (*p == '\\' || *p == '/') {
last_sep = p;
}
}
*last_sep = '\0';
lstrcatA(path, "\\config.json");
return path;
}
std::string normalize_base_url(std::string value) {
if (value.empty()) {
return value;
}
constexpr const char* kHttp = "http://";
constexpr const char* kHttps = "https://";
if (value.rfind(kHttp, 0) == 0) {
value.erase(0, lstrlenA(kHttp));
} else if (value.rfind(kHttps, 0) == 0) {
value.erase(0, lstrlenA(kHttps));
}
while (!value.empty() && value.front() == '/') {
value.erase(value.begin());
}
if (value.empty()) {
return value;
}
if (value.back() != '/') {
value.push_back('/');
}
return value;
}
void read_string(const rapidjson::Document& document, const char* key, std::string& out) {
if (!document.HasMember(key) || !document[key].IsString()) {
return;
}
out = normalize_base_url(document[key].GetString());
}
}
void load() {
if (g_loaded) {
return;
}
g_loaded = true;
const auto config_path = get_config_path();
if (config_path.empty()) {
hook_log("[cgss-http-hook] failed to resolve config.json path");
return;
}
std::ifstream config_stream(config_path);
if (!config_stream.is_open()) {
hook_log("[cgss-http-hook] config.json not found, using defaults");
return;
}
rapidjson::IStreamWrapper wrapper(config_stream);
rapidjson::Document document;
document.ParseStream(wrapper);
if (document.HasParseError() || !document.IsObject()) {
hook_log("[cgss-http-hook] failed to parse config.json");
return;
}
read_string(document, "api_url", g_urls.api_url);
read_string(document, "asset_url", g_urls.asset_url);
if (!g_urls.api_url.empty()) {
hook_logf("[cgss-http-hook] normalized api_url=%s", g_urls.api_url.c_str());
}
if (!g_urls.asset_url.empty()) {
hook_logf("[cgss-http-hook] normalized asset_url=%s", g_urls.asset_url.c_str());
}
}
const OverrideUrls& get() {
return g_urls;
}
}
+11
View File
@@ -0,0 +1,11 @@
#pragma once
namespace config {
struct OverrideUrls {
std::string api_url;
std::string asset_url;
};
void load();
const OverrideUrls& get();
}
+57
View File
@@ -1,7 +1,10 @@
#include "stdinclude.hpp" #include "stdinclude.hpp"
#include "config.hpp"
namespace { namespace {
using GetSchemeTypeFn = int (*)(); using GetSchemeTypeFn = int (*)();
using GetStringFn = void* (*)();
constexpr int kHttpSchemeType = 0; constexpr int kHttpSchemeType = 0;
constexpr DWORD kHookPollIntervalMs = 500; constexpr DWORD kHookPollIntervalMs = 500;
constexpr DWORD kGameAssemblySettleDelayMs = 5000; constexpr DWORD kGameAssemblySettleDelayMs = 5000;
@@ -9,6 +12,8 @@ namespace {
volatile LONG g_hook_installed = 0; volatile LONG g_hook_installed = 0;
GetSchemeTypeFn g_orig_get_scheme_type = nullptr; GetSchemeTypeFn g_orig_get_scheme_type = nullptr;
GetStringFn g_orig_get_application_server_url = nullptr;
GetStringFn g_orig_get_resource_server_url = nullptr;
volatile LONG g_logged_gameassembly = 0; volatile LONG g_logged_gameassembly = 0;
volatile LONG g_exports_state = 0; volatile LONG g_exports_state = 0;
ULONGLONG g_gameassembly_seen_tick = 0; ULONGLONG g_gameassembly_seen_tick = 0;
@@ -21,6 +26,22 @@ namespace {
return kHttpSchemeType; return kHttpSchemeType;
} }
void* get_application_server_url_hook() {
const auto& urls = config::get();
if (!urls.api_url.empty()) {
return il2cpp_symbols::new_string(urls.api_url.c_str());
}
return g_orig_get_application_server_url ? g_orig_get_application_server_url() : nullptr;
}
void* get_resource_server_url_hook() {
const auto& urls = config::get();
if (!urls.asset_url.empty()) {
return il2cpp_symbols::new_string(urls.asset_url.c_str());
}
return g_orig_get_resource_server_url ? g_orig_get_resource_server_url() : nullptr;
}
void patch_custom_preference_scheme() { void patch_custom_preference_scheme() {
auto custom_preference = il2cpp_symbols::get_class( auto custom_preference = il2cpp_symbols::get_class(
"Assembly-CSharp.dll", "Cute", "CustomPreference" "Assembly-CSharp.dll", "Cute", "CustomPreference"
@@ -77,6 +98,13 @@ namespace {
return; return;
} }
auto get_application_server_url_addr = il2cpp_symbols::get_method_pointer(
"Assembly-CSharp.dll", "Stage", "NetworkUtil", "GetApplicationServerUrl", 0
);
auto get_resource_server_url_addr = il2cpp_symbols::get_method_pointer(
"Assembly-CSharp.dll", "Stage", "NetworkUtil", "GetResourceServerUrl", 0
);
auto mh_status = MH_Initialize(); auto mh_status = MH_Initialize();
if (mh_status != MH_OK && mh_status != MH_ERROR_ALREADY_INITIALIZED) { if (mh_status != MH_OK && mh_status != MH_ERROR_ALREADY_INITIALIZED) {
hook_log("[cgss-http-hook] MH_Initialize failed"); hook_log("[cgss-http-hook] MH_Initialize failed");
@@ -99,6 +127,34 @@ namespace {
return; return;
} }
if (get_application_server_url_addr) {
auto create_status = MH_CreateHook(
reinterpret_cast<void*>(get_application_server_url_addr),
reinterpret_cast<void*>(&get_application_server_url_hook),
reinterpret_cast<void**>(&g_orig_get_application_server_url)
);
if (create_status == MH_OK || create_status == MH_ERROR_ALREADY_CREATED) {
auto enable_status = MH_EnableHook(reinterpret_cast<void*>(get_application_server_url_addr));
if (enable_status == MH_OK || enable_status == MH_ERROR_ENABLED) {
hook_log("[cgss-http-hook] hooked Stage.NetworkUtil.GetApplicationServerUrl");
}
}
}
if (get_resource_server_url_addr) {
auto create_status = MH_CreateHook(
reinterpret_cast<void*>(get_resource_server_url_addr),
reinterpret_cast<void*>(&get_resource_server_url_hook),
reinterpret_cast<void**>(&g_orig_get_resource_server_url)
);
if (create_status == MH_OK || create_status == MH_ERROR_ALREADY_CREATED) {
auto enable_status = MH_EnableHook(reinterpret_cast<void*>(get_resource_server_url_addr));
if (enable_status == MH_OK || enable_status == MH_ERROR_ENABLED) {
hook_log("[cgss-http-hook] hooked Stage.NetworkUtil.GetResourceServerUrl");
}
}
}
InterlockedExchange(&g_hook_installed, 1); InterlockedExchange(&g_hook_installed, 1);
hook_log("[cgss-http-hook] hooked Stage.NetworkUtil.GetSchemeType"); hook_log("[cgss-http-hook] hooked Stage.NetworkUtil.GetSchemeType");
} }
@@ -117,5 +173,6 @@ namespace {
} }
void start_hook_thread() { void start_hook_thread() {
config::load();
CreateThread(nullptr, 0, init_thread, nullptr, 0, nullptr); CreateThread(nullptr, 0, init_thread, nullptr, 0, nullptr);
} }
+11 -1
View File
@@ -10,6 +10,7 @@ il2cpp_class_get_field_from_name_t il2cpp_symbols::il2cpp_class_get_field_from_n
il2cpp_method_get_name_t il2cpp_symbols::il2cpp_method_get_name = nullptr; il2cpp_method_get_name_t il2cpp_symbols::il2cpp_method_get_name = nullptr;
il2cpp_thread_attach_t il2cpp_symbols::il2cpp_thread_attach = nullptr; il2cpp_thread_attach_t il2cpp_symbols::il2cpp_thread_attach = nullptr;
il2cpp_thread_current_t il2cpp_symbols::il2cpp_thread_current = nullptr; il2cpp_thread_current_t il2cpp_symbols::il2cpp_thread_current = nullptr;
il2cpp_string_new_t il2cpp_symbols::il2cpp_string_new = nullptr;
il2cpp_runtime_class_init_t il2cpp_symbols::il2cpp_runtime_class_init = nullptr; il2cpp_runtime_class_init_t il2cpp_symbols::il2cpp_runtime_class_init = nullptr;
il2cpp_field_get_flags_t il2cpp_symbols::il2cpp_field_get_flags = nullptr; il2cpp_field_get_flags_t il2cpp_symbols::il2cpp_field_get_flags = nullptr;
il2cpp_field_static_set_value_t il2cpp_symbols::il2cpp_field_static_set_value = nullptr; il2cpp_field_static_set_value_t il2cpp_symbols::il2cpp_field_static_set_value = nullptr;
@@ -33,6 +34,7 @@ void il2cpp_symbols::init(HMODULE game_module) {
resolve(game_module, "il2cpp_method_get_name", il2cpp_method_get_name); resolve(game_module, "il2cpp_method_get_name", il2cpp_method_get_name);
resolve(game_module, "il2cpp_thread_attach", il2cpp_thread_attach); resolve(game_module, "il2cpp_thread_attach", il2cpp_thread_attach);
resolve(game_module, "il2cpp_thread_current", il2cpp_thread_current); resolve(game_module, "il2cpp_thread_current", il2cpp_thread_current);
resolve(game_module, "il2cpp_string_new", il2cpp_string_new);
resolve(game_module, "il2cpp_runtime_class_init", il2cpp_runtime_class_init); resolve(game_module, "il2cpp_runtime_class_init", il2cpp_runtime_class_init);
resolve(game_module, "il2cpp_field_get_flags", il2cpp_field_get_flags); resolve(game_module, "il2cpp_field_get_flags", il2cpp_field_get_flags);
resolve(game_module, "il2cpp_field_static_set_value", il2cpp_field_static_set_value); resolve(game_module, "il2cpp_field_static_set_value", il2cpp_field_static_set_value);
@@ -42,7 +44,8 @@ bool il2cpp_symbols::ready() {
return il2cpp_domain_get && il2cpp_domain_assembly_open && il2cpp_assembly_get_image && return il2cpp_domain_get && il2cpp_domain_assembly_open && il2cpp_assembly_get_image &&
il2cpp_class_from_name && il2cpp_class_get_method_from_name && il2cpp_class_from_name && il2cpp_class_get_method_from_name &&
il2cpp_class_get_field_from_name && il2cpp_thread_attach && il2cpp_class_get_field_from_name && il2cpp_thread_attach &&
il2cpp_runtime_class_init && il2cpp_field_get_flags && il2cpp_field_static_set_value; il2cpp_string_new && il2cpp_runtime_class_init &&
il2cpp_field_get_flags && il2cpp_field_static_set_value;
} }
bool il2cpp_symbols::domain_ready() { bool il2cpp_symbols::domain_ready() {
@@ -69,6 +72,13 @@ bool il2cpp_symbols::attach_thread() {
return true; return true;
} }
void* il2cpp_symbols::new_string(const char* value) {
if (!value || !attach_thread() || !il2cpp_string_new) {
return nullptr;
}
return il2cpp_string_new(value);
}
void* il2cpp_symbols::get_class(const char* assembly_name, const char* namespaze, const char* klass_name) { void* il2cpp_symbols::get_class(const char* assembly_name, const char* namespaze, const char* klass_name) {
if (!attach_thread()) { if (!attach_thread()) {
hook_log("[cgss-http-hook] il2cpp domain not ready"); hook_log("[cgss-http-hook] il2cpp domain not ready");
+3
View File
@@ -15,6 +15,7 @@ typedef FieldInfo* (*il2cpp_class_get_field_from_name_t)(void* klass, const char
typedef const char* (*il2cpp_method_get_name_t)(MethodInfo* method); typedef const char* (*il2cpp_method_get_name_t)(MethodInfo* method);
typedef void (*il2cpp_thread_attach_t)(void* domain); typedef void (*il2cpp_thread_attach_t)(void* domain);
typedef void* (*il2cpp_thread_current_t)(); typedef void* (*il2cpp_thread_current_t)();
typedef void* (*il2cpp_string_new_t)(const char* str);
typedef void (*il2cpp_runtime_class_init_t)(void* klass); typedef void (*il2cpp_runtime_class_init_t)(void* klass);
typedef uint32_t (*il2cpp_field_get_flags_t)(FieldInfo* field); typedef uint32_t (*il2cpp_field_get_flags_t)(FieldInfo* field);
typedef void (*il2cpp_field_static_set_value_t)(FieldInfo* field, void* value); typedef void (*il2cpp_field_static_set_value_t)(FieldInfo* field, void* value);
@@ -51,6 +52,7 @@ namespace il2cpp_symbols {
extern il2cpp_method_get_name_t il2cpp_method_get_name; extern il2cpp_method_get_name_t il2cpp_method_get_name;
extern il2cpp_thread_attach_t il2cpp_thread_attach; extern il2cpp_thread_attach_t il2cpp_thread_attach;
extern il2cpp_thread_current_t il2cpp_thread_current; extern il2cpp_thread_current_t il2cpp_thread_current;
extern il2cpp_string_new_t il2cpp_string_new;
extern il2cpp_runtime_class_init_t il2cpp_runtime_class_init; extern il2cpp_runtime_class_init_t il2cpp_runtime_class_init;
extern il2cpp_field_get_flags_t il2cpp_field_get_flags; extern il2cpp_field_get_flags_t il2cpp_field_get_flags;
extern il2cpp_field_static_set_value_t il2cpp_field_static_set_value; extern il2cpp_field_static_set_value_t il2cpp_field_static_set_value;
@@ -60,6 +62,7 @@ namespace il2cpp_symbols {
bool ready(); bool ready();
bool domain_ready(); bool domain_ready();
bool attach_thread(); bool attach_thread();
void* new_string(const char* value);
void* get_class(const char* assembly_name, const char* namespaze, const char* klass_name); void* get_class(const char* assembly_name, const char* namespaze, const char* klass_name);
bool set_static_int_field(void* klass, const char* field_name, int value); bool set_static_int_field(void* klass, const char* field_name, int value);
uintptr_t get_method_pointer(const char* assembly_name, const char* namespaze, uintptr_t get_method_pointer(const char* assembly_name, const char* namespaze,
+16
View File
@@ -1,5 +1,7 @@
#include "stdinclude.hpp" #include "stdinclude.hpp"
#include <cstdarg>
namespace { namespace {
HANDLE g_log_file = INVALID_HANDLE_VALUE; HANDLE g_log_file = INVALID_HANDLE_VALUE;
@@ -53,3 +55,17 @@ void hook_log(const char* message) {
WriteFile(log_file, message, lstrlenA(message), &ignored, nullptr); WriteFile(log_file, message, lstrlenA(message), &ignored, nullptr);
WriteFile(log_file, "\r\n", 2, &ignored, nullptr); WriteFile(log_file, "\r\n", 2, &ignored, nullptr);
} }
void hook_logf(const char* format, ...) {
if (!format) {
return;
}
char buffer[1024] = {};
va_list args;
va_start(args, format);
wvsprintfA(buffer, format, args);
va_end(args);
hook_log(buffer);
}
+5
View File
@@ -3,9 +3,14 @@
#include <windows.h> #include <windows.h>
#include <cstring> #include <cstring>
#include <fstream>
#include <string>
#include <MinHook.h> #include <MinHook.h>
#include <rapidjson/document.h>
#include <rapidjson/istreamwrapper.h>
#include "il2cpp_symbols.hpp" #include "il2cpp_symbols.hpp"
void hook_log(const char* message); void hook_log(const char* message);
void hook_logf(const char* format, ...);