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
+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 "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
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_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");
+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 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
View File
@@ -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);
}
+5
View File
@@ -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, ...);