@@ -11,6 +11,7 @@ add_library(version SHARED
|
||||
src/proxy.cpp
|
||||
src/hook.cpp
|
||||
src/log.cpp
|
||||
src/config.cpp
|
||||
src/il2cpp_symbols.cpp
|
||||
src/version.rc
|
||||
src/version.def
|
||||
@@ -25,6 +26,7 @@ target_include_directories(version PRIVATE
|
||||
deps/minhook/include
|
||||
deps/minhook/src
|
||||
deps/minhook/src/hde
|
||||
deps/rapidjson/include
|
||||
)
|
||||
|
||||
target_compile_definitions(version PRIVATE
|
||||
|
||||
@@ -8,6 +8,7 @@ CXX := x86_64-w64-mingw32-g++
|
||||
WINDRES := x86_64-w64-mingw32-windres
|
||||
|
||||
INCLUDES := -Isrc -Ideps/minhook/include -Ideps/minhook/src -Ideps/minhook/src/hde
|
||||
INCLUDES += -Ideps/rapidjson/include
|
||||
DEFINES := -DNOMINMAX -DWIN32_LEAN_AND_MEAN
|
||||
CFLAGS := -std=gnu11 $(DEFINES) $(INCLUDES)
|
||||
CXXFLAGS := -std=gnu++20 -fno-exceptions -fno-rtti $(DEFINES) $(INCLUDES)
|
||||
@@ -34,6 +35,7 @@ CXX_SRCS := \
|
||||
src/proxy.cpp \
|
||||
src/hook.cpp \
|
||||
src/log.cpp \
|
||||
src/config.cpp \
|
||||
src/il2cpp_symbols.cpp
|
||||
|
||||
RC_SRCS := src/version.rc
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
- 转发 `UnityPlayer.dll` 实际导入的少量 `version.dll` 导出函数
|
||||
- 等待 IL2CPP 运行时初始化稳定
|
||||
- 运行时 hook `Stage.NetworkUtil.GetSchemeType()`,强制返回 `0`
|
||||
- 支持通过 `config.json` 覆盖 `api_url` 和 `asset_url`
|
||||
|
||||
**目录结构**
|
||||
- `src/`:代理、日志、IL2CPP 符号解析、hook 逻辑
|
||||
@@ -16,6 +17,7 @@
|
||||
- `GameAssembly.dll` 出现时,IL2CPP runtime 可能还不能安全访问,因此 hook 线程会额外等待一小段时间
|
||||
- 运行时日志会写到游戏目录下的 `cgss-http-hook.log`
|
||||
- 生成的 DLL 包含 Windows 版本资源,定义在 `src/version.rc`
|
||||
- `config.json` 中的 URL 会自动规范化:若写了 `http://` 或 `https://` 会自动去掉,若末尾缺少 `/` 会自动补上
|
||||
|
||||
**构建**
|
||||
```sh
|
||||
@@ -28,7 +30,20 @@ cmake --build build
|
||||
**输出**
|
||||
- `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` 同目录
|
||||
2. 启动游戏
|
||||
3. 如果 hook 未生效,查看 `cgss-http-hook.log`
|
||||
2. 如需覆盖地址,在同目录放置 `config.json`
|
||||
3. 启动游戏
|
||||
4. 如果 hook 未生效,查看 `cgss-http-hook.log`
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"api_url": "apis.game.starlight-stage.jp/",
|
||||
"asset_url": "asset-starlight-stage.akamaized.net/"
|
||||
}
|
||||
+105
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
namespace config {
|
||||
struct OverrideUrls {
|
||||
std::string api_url;
|
||||
std::string asset_url;
|
||||
};
|
||||
|
||||
void load();
|
||||
const OverrideUrls& get();
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
#include "stdinclude.hpp"
|
||||
|
||||
#include "config.hpp"
|
||||
|
||||
namespace {
|
||||
using GetSchemeTypeFn = int (*)();
|
||||
using GetStringFn = void* (*)();
|
||||
constexpr int kHttpSchemeType = 0;
|
||||
constexpr DWORD kHookPollIntervalMs = 500;
|
||||
constexpr DWORD kGameAssemblySettleDelayMs = 5000;
|
||||
@@ -9,6 +12,8 @@ namespace {
|
||||
|
||||
volatile LONG g_hook_installed = 0;
|
||||
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_exports_state = 0;
|
||||
ULONGLONG g_gameassembly_seen_tick = 0;
|
||||
@@ -21,6 +26,22 @@ namespace {
|
||||
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() {
|
||||
auto custom_preference = il2cpp_symbols::get_class(
|
||||
"Assembly-CSharp.dll", "Cute", "CustomPreference"
|
||||
@@ -77,6 +98,13 @@ namespace {
|
||||
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();
|
||||
if (mh_status != MH_OK && mh_status != MH_ERROR_ALREADY_INITIALIZED) {
|
||||
hook_log("[cgss-http-hook] MH_Initialize failed");
|
||||
@@ -99,6 +127,34 @@ namespace {
|
||||
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);
|
||||
hook_log("[cgss-http-hook] hooked Stage.NetworkUtil.GetSchemeType");
|
||||
}
|
||||
@@ -117,5 +173,6 @@ namespace {
|
||||
}
|
||||
|
||||
void start_hook_thread() {
|
||||
config::load();
|
||||
CreateThread(nullptr, 0, init_thread, nullptr, 0, nullptr);
|
||||
}
|
||||
|
||||
+11
-1
@@ -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_thread_attach_t il2cpp_symbols::il2cpp_thread_attach = 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_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;
|
||||
@@ -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_thread_attach", il2cpp_thread_attach);
|
||||
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_field_get_flags", il2cpp_field_get_flags);
|
||||
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 &&
|
||||
il2cpp_class_from_name && il2cpp_class_get_method_from_name &&
|
||||
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() {
|
||||
@@ -69,6 +72,13 @@ bool il2cpp_symbols::attach_thread() {
|
||||
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) {
|
||||
if (!attach_thread()) {
|
||||
hook_log("[cgss-http-hook] il2cpp domain not ready");
|
||||
|
||||
@@ -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 void (*il2cpp_thread_attach_t)(void* domain);
|
||||
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 uint32_t (*il2cpp_field_get_flags_t)(FieldInfo* field);
|
||||
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_thread_attach_t il2cpp_thread_attach;
|
||||
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_field_get_flags_t il2cpp_field_get_flags;
|
||||
extern il2cpp_field_static_set_value_t il2cpp_field_static_set_value;
|
||||
@@ -60,6 +62,7 @@ namespace il2cpp_symbols {
|
||||
bool ready();
|
||||
bool domain_ready();
|
||||
bool attach_thread();
|
||||
void* new_string(const char* value);
|
||||
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);
|
||||
uintptr_t get_method_pointer(const char* assembly_name, const char* namespaze,
|
||||
|
||||
+16
@@ -1,5 +1,7 @@
|
||||
#include "stdinclude.hpp"
|
||||
|
||||
#include <cstdarg>
|
||||
|
||||
namespace {
|
||||
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, "\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);
|
||||
}
|
||||
|
||||
@@ -3,9 +3,14 @@
|
||||
#include <windows.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
#include <MinHook.h>
|
||||
#include <rapidjson/document.h>
|
||||
#include <rapidjson/istreamwrapper.h>
|
||||
|
||||
#include "il2cpp_symbols.hpp"
|
||||
|
||||
void hook_log(const char* message);
|
||||
void hook_logf(const char* format, ...);
|
||||
|
||||
Reference in New Issue
Block a user