Add external borderless helper
Signed-off-by: Sean Du <do4suki@gmail.com>
This commit is contained in:
@@ -21,6 +21,10 @@ add_library(version SHARED
|
|||||||
deps/minhook/src/hde/hde64.c
|
deps/minhook/src/hde/hde64.c
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_executable(cgss-borderless-helper WIN32
|
||||||
|
src/helper_main.cpp
|
||||||
|
)
|
||||||
|
|
||||||
target_include_directories(version PRIVATE
|
target_include_directories(version PRIVATE
|
||||||
src
|
src
|
||||||
deps/minhook/include
|
deps/minhook/include
|
||||||
@@ -29,6 +33,10 @@ target_include_directories(version PRIVATE
|
|||||||
deps/rapidjson/include
|
deps/rapidjson/include
|
||||||
)
|
)
|
||||||
|
|
||||||
|
target_include_directories(cgss-borderless-helper PRIVATE
|
||||||
|
src
|
||||||
|
)
|
||||||
|
|
||||||
target_compile_definitions(version PRIVATE
|
target_compile_definitions(version PRIVATE
|
||||||
NOMINMAX
|
NOMINMAX
|
||||||
WIN32_LEAN_AND_MEAN
|
WIN32_LEAN_AND_MEAN
|
||||||
@@ -39,18 +47,36 @@ target_link_libraries(version PRIVATE
|
|||||||
kernel32
|
kernel32
|
||||||
)
|
)
|
||||||
|
|
||||||
|
target_link_libraries(cgss-borderless-helper PRIVATE
|
||||||
|
user32
|
||||||
|
kernel32
|
||||||
|
shell32
|
||||||
|
)
|
||||||
|
|
||||||
set_target_properties(version PROPERTIES
|
set_target_properties(version PROPERTIES
|
||||||
OUTPUT_NAME "version"
|
OUTPUT_NAME "version"
|
||||||
PREFIX ""
|
PREFIX ""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set_target_properties(cgss-borderless-helper PROPERTIES
|
||||||
|
OUTPUT_NAME "cgss-borderless-helper"
|
||||||
|
)
|
||||||
|
|
||||||
if(MINGW)
|
if(MINGW)
|
||||||
target_compile_options(version PRIVATE
|
target_compile_options(version PRIVATE
|
||||||
"$<$<COMPILE_LANGUAGE:CXX>:-fno-exceptions>"
|
"$<$<COMPILE_LANGUAGE:CXX>:-fno-exceptions>"
|
||||||
"$<$<COMPILE_LANGUAGE:CXX>:-fno-rtti>"
|
"$<$<COMPILE_LANGUAGE:CXX>:-fno-rtti>"
|
||||||
)
|
)
|
||||||
|
target_compile_options(cgss-borderless-helper PRIVATE
|
||||||
|
-fno-exceptions
|
||||||
|
-fno-rtti
|
||||||
|
)
|
||||||
target_link_options(version PRIVATE
|
target_link_options(version PRIVATE
|
||||||
"-static-libgcc"
|
"-static-libgcc"
|
||||||
"-static-libstdc++"
|
"-static-libstdc++"
|
||||||
)
|
)
|
||||||
|
target_link_options(cgss-borderless-helper PRIVATE
|
||||||
|
"-static-libgcc"
|
||||||
|
"-static-libstdc++"
|
||||||
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
TARGET := build/version.dll
|
TARGET := build/version.dll
|
||||||
|
HELPER_TARGET := build/cgss-borderless-helper.exe
|
||||||
BUILD_DIR := build
|
BUILD_DIR := build
|
||||||
OBJ_DIR := $(BUILD_DIR)/obj
|
OBJ_DIR := $(BUILD_DIR)/obj
|
||||||
BUILD ?= release
|
BUILD ?= release
|
||||||
@@ -14,6 +15,8 @@ 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)
|
||||||
LDFLAGS := -shared -static-libgcc -static-libstdc++
|
LDFLAGS := -shared -static-libgcc -static-libstdc++
|
||||||
LDLIBS := -luser32 -lkernel32
|
LDLIBS := -luser32 -lkernel32
|
||||||
|
HELPER_LDFLAGS := -static-libgcc -static-libstdc++ -mwindows
|
||||||
|
HELPER_LDLIBS := -luser32 -lkernel32 -lshell32
|
||||||
|
|
||||||
ifeq ($(BUILD),debug)
|
ifeq ($(BUILD),debug)
|
||||||
DEFINES += -D_DEBUG
|
DEFINES += -D_DEBUG
|
||||||
@@ -38,18 +41,22 @@ CXX_SRCS := \
|
|||||||
src/config.cpp \
|
src/config.cpp \
|
||||||
src/il2cpp_symbols.cpp
|
src/il2cpp_symbols.cpp
|
||||||
|
|
||||||
|
HELPER_CXX_SRCS := \
|
||||||
|
src/helper_main.cpp
|
||||||
|
|
||||||
RC_SRCS := src/version.rc
|
RC_SRCS := src/version.rc
|
||||||
DEF_FILE := src/version.def
|
DEF_FILE := src/version.def
|
||||||
|
|
||||||
C_OBJS := $(patsubst %.c,$(OBJ_DIR)/%.o,$(C_SRCS))
|
C_OBJS := $(patsubst %.c,$(OBJ_DIR)/%.o,$(C_SRCS))
|
||||||
CXX_OBJS := $(patsubst %.cpp,$(OBJ_DIR)/%.o,$(CXX_SRCS))
|
CXX_OBJS := $(patsubst %.cpp,$(OBJ_DIR)/%.o,$(CXX_SRCS))
|
||||||
|
HELPER_CXX_OBJS := $(patsubst %.cpp,$(OBJ_DIR)/%.o,$(HELPER_CXX_SRCS))
|
||||||
RC_OBJS := $(patsubst %.rc,$(OBJ_DIR)/%.res,$(RC_SRCS))
|
RC_OBJS := $(patsubst %.rc,$(OBJ_DIR)/%.res,$(RC_SRCS))
|
||||||
OBJS := $(C_OBJS) $(CXX_OBJS) $(RC_OBJS)
|
OBJS := $(C_OBJS) $(CXX_OBJS) $(RC_OBJS)
|
||||||
|
|
||||||
.PHONY: all release debug clean
|
.PHONY: all release debug clean
|
||||||
|
|
||||||
all: $(TARGET)
|
all: $(TARGET) $(HELPER_TARGET)
|
||||||
release: $(TARGET)
|
release: $(TARGET) $(HELPER_TARGET)
|
||||||
debug:
|
debug:
|
||||||
$(MAKE) BUILD=debug
|
$(MAKE) BUILD=debug
|
||||||
|
|
||||||
@@ -57,6 +64,10 @@ $(TARGET): $(OBJS) $(DEF_FILE)
|
|||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(CXX) $(LDFLAGS) -o $@ $(OBJS) $(DEF_FILE) $(LDLIBS)
|
$(CXX) $(LDFLAGS) -o $@ $(OBJS) $(DEF_FILE) $(LDLIBS)
|
||||||
|
|
||||||
|
$(HELPER_TARGET): $(HELPER_CXX_OBJS)
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(CXX) $(HELPER_LDFLAGS) -o $@ $(HELPER_CXX_OBJS) $(HELPER_LDLIBS)
|
||||||
|
|
||||||
$(OBJ_DIR)/%.o: %.c
|
$(OBJ_DIR)/%.o: %.c
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(CC) $(CFLAGS) -c $< -o $@
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
- 等待 IL2CPP 运行时初始化稳定
|
- 等待 IL2CPP 运行时初始化稳定
|
||||||
- 可选地 hook `Stage.NetworkUtil.GetSchemeType()`,在需要时强制切到 `http`
|
- 可选地 hook `Stage.NetworkUtil.GetSchemeType()`,在需要时强制切到 `http`
|
||||||
- 支持通过 `config.json` 覆盖 `api_url` 和 `asset_url`
|
- 支持通过 `config.json` 覆盖 `api_url` 和 `asset_url`
|
||||||
|
- 自动拉起同目录下的 `cgss-borderless-helper.exe`,用外部 helper 处理 `F11` 无边框全屏
|
||||||
|
|
||||||
**目录结构**
|
**目录结构**
|
||||||
- `src/`:代理、日志、IL2CPP 符号解析、hook 逻辑
|
- `src/`:代理、日志、IL2CPP 符号解析、hook 逻辑
|
||||||
@@ -28,15 +29,22 @@ cmake -S . -B build -G Ninja -DCMAKE_SYSTEM_NAME=Windows \
|
|||||||
cmake --build build
|
cmake --build build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
或直接使用:
|
||||||
|
```sh
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
**输出**
|
**输出**
|
||||||
- `build/version.dll`
|
- `build/version.dll`
|
||||||
|
- `build/cgss-borderless-helper.exe`
|
||||||
|
|
||||||
**config.json**
|
**config.json**
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"force_http": true,
|
"force_http": true,
|
||||||
"api_url": "apis.game.starlight-stage.jp/",
|
"api_url": "apis.game.starlight-stage.jp/",
|
||||||
"asset_url": "asset-starlight-stage.akamaized.net/"
|
"asset_url": "asset-starlight-stage.akamaized.net/",
|
||||||
|
"launch_borderless_helper": true
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -44,12 +52,20 @@ cmake --build build
|
|||||||
- `force_http`:是否强制将 scheme 改成 `http`。默认 `true`
|
- `force_http`:是否强制将 scheme 改成 `http`。默认 `true`
|
||||||
- `api_url`:API 主机名,按原始字段格式填写,不带 scheme
|
- `api_url`:API 主机名,按原始字段格式填写,不带 scheme
|
||||||
- `asset_url`:资源主机名,按原始字段格式填写,不带 scheme
|
- `asset_url`:资源主机名,按原始字段格式填写,不带 scheme
|
||||||
|
- `launch_borderless_helper`:是否自动启动 `cgss-borderless-helper.exe`。默认 `true`
|
||||||
|
|
||||||
**使用方法**
|
**使用方法**
|
||||||
1. 将 `build/version.dll` 复制到 `imascgstage.exe` 同目录
|
1. 将 `build/version.dll` 和 `build/cgss-borderless-helper.exe` 一起复制到 `imascgstage.exe` 同目录
|
||||||
2. 如需覆盖地址,在同目录放置 `config.json`
|
2. 如需覆盖地址,在同目录放置 `config.json`
|
||||||
3. 启动游戏
|
3. 启动游戏
|
||||||
4. 如果 hook 未生效,查看 `cgss-dmm-hook.log`
|
4. 进入游戏后按 `F11` 切换无边框全屏
|
||||||
|
5. 如果 hook 未生效,查看 `cgss-dmm-hook.log` 和 `cgss-borderless-helper.log`
|
||||||
|
|
||||||
|
说明:
|
||||||
|
- `cgss-borderless-helper.exe` 现在只负责外部窗口 borderless 行为,不再修改 Unity 内部 `Screen.SetResolution`。
|
||||||
|
- 这样更接近 Borderless Gaming 的工作方式,也能减少退出时的干扰。
|
||||||
|
- `version.dll` 只在 `imascgstage.exe` 主进程中继续启动 hook 和 helper;被其他进程间接加载时会跳过。
|
||||||
|
|
||||||
**致谢**
|
**致谢**
|
||||||
- 本项目的 `version.dll` 代理注入思路与运行时 IL2CPP hook 方向,受 `gkms-localify-dmm` 项目启发。
|
- 本项目的 `version.dll` 代理注入思路与运行时 IL2CPP hook 方向,受 `gkms-localify-dmm` 项目启发。
|
||||||
|
- `cgss-borderless-helper.exe` 的外部无边框全屏处理思路,参考了 Borderless Gaming 项目;本项目同样按 GPL 开源。
|
||||||
|
|||||||
+2
-1
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"force_http": true,
|
"force_http": true,
|
||||||
"api_url": "apis.game.starlight-stage.jp/",
|
"api_url": "apis.game.starlight-stage.jp/",
|
||||||
"asset_url": "asset-starlight-stage.akamaized.net/"
|
"asset_url": "asset-starlight-stage.akamaized.net/",
|
||||||
|
"launch_borderless_helper": true
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-1
@@ -59,7 +59,8 @@ namespace config {
|
|||||||
"{\r\n"
|
"{\r\n"
|
||||||
" \"force_http\": false,\r\n"
|
" \"force_http\": false,\r\n"
|
||||||
" \"api_url\": \"apis.game.starlight-stage.jp/\",\r\n"
|
" \"api_url\": \"apis.game.starlight-stage.jp/\",\r\n"
|
||||||
" \"asset_url\": \"asset-starlight-stage.akamaized.net/\"\r\n"
|
" \"asset_url\": \"asset-starlight-stage.akamaized.net/\",\r\n"
|
||||||
|
" \"launch_borderless_helper\": true\r\n"
|
||||||
"}\r\n";
|
"}\r\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,8 +115,13 @@ namespace config {
|
|||||||
read_bool(document, "force_http", g_urls.force_http);
|
read_bool(document, "force_http", g_urls.force_http);
|
||||||
read_string(document, "api_url", g_urls.api_url);
|
read_string(document, "api_url", g_urls.api_url);
|
||||||
read_string(document, "asset_url", g_urls.asset_url);
|
read_string(document, "asset_url", g_urls.asset_url);
|
||||||
|
read_bool(document, "launch_borderless_helper", g_urls.launch_borderless_helper);
|
||||||
|
|
||||||
hook_logf("[cgss-dmm-hook] force_http=%s", g_urls.force_http ? "true" : "false");
|
hook_logf("[cgss-dmm-hook] force_http=%s", g_urls.force_http ? "true" : "false");
|
||||||
|
hook_logf(
|
||||||
|
"[cgss-dmm-hook] launch_borderless_helper=%s",
|
||||||
|
g_urls.launch_borderless_helper ? "true" : "false"
|
||||||
|
);
|
||||||
|
|
||||||
if (!g_urls.api_url.empty()) {
|
if (!g_urls.api_url.empty()) {
|
||||||
hook_logf("[cgss-dmm-hook] normalized api_url=%s", g_urls.api_url.c_str());
|
hook_logf("[cgss-dmm-hook] normalized api_url=%s", g_urls.api_url.c_str());
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace config {
|
|||||||
bool force_http = true;
|
bool force_http = true;
|
||||||
std::string api_url;
|
std::string api_url;
|
||||||
std::string asset_url;
|
std::string asset_url;
|
||||||
|
bool launch_borderless_helper = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
void load();
|
void load();
|
||||||
|
|||||||
@@ -0,0 +1,385 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
#include <shellapi.h>
|
||||||
|
|
||||||
|
#include <cstdarg>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr DWORD kWindowPollIntervalMs = 500;
|
||||||
|
constexpr int kWindowPollMaxAttempts = 240;
|
||||||
|
constexpr DWORD kHotkeyPollIntervalMs = 50;
|
||||||
|
constexpr DWORD kMaintainIntervalMs = 500;
|
||||||
|
constexpr DWORD kDelayedStyleApplyMs = 4;
|
||||||
|
constexpr int kToggleKey = VK_F11;
|
||||||
|
|
||||||
|
HWND g_main_window = nullptr;
|
||||||
|
bool g_borderless_active = false;
|
||||||
|
bool g_saved_state_valid = false;
|
||||||
|
RECT g_saved_window_rect = {};
|
||||||
|
LONG_PTR g_saved_style = 0;
|
||||||
|
LONG_PTR g_saved_exstyle = 0;
|
||||||
|
DWORD g_target_process_id = 0;
|
||||||
|
|
||||||
|
std::wstring get_window_class_name(HWND hwnd) {
|
||||||
|
wchar_t buffer[256] = {};
|
||||||
|
int len = GetClassNameW(hwnd, buffer, static_cast<int>(std::size(buffer)));
|
||||||
|
if (len <= 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return std::wstring(buffer, buffer + len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void helper_logf(const char* format, ...) {
|
||||||
|
char exe_path[MAX_PATH] = {};
|
||||||
|
DWORD len = GetModuleFileNameA(nullptr, exe_path, MAX_PATH);
|
||||||
|
if (len == 0 || len >= MAX_PATH) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* last_sep = exe_path;
|
||||||
|
for (char* p = exe_path; *p != '\0'; ++p) {
|
||||||
|
if (*p == '\\' || *p == '/') {
|
||||||
|
last_sep = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*last_sep = '\0';
|
||||||
|
lstrcatA(exe_path, "\\cgss-borderless-helper.log");
|
||||||
|
|
||||||
|
char message[1024] = {};
|
||||||
|
va_list args;
|
||||||
|
va_start(args, format);
|
||||||
|
_vsnprintf_s(message, sizeof(message), _TRUNCATE, format, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
std::ofstream stream(exe_path, std::ios::app | std::ios::binary);
|
||||||
|
if (!stream.is_open()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SYSTEMTIME st = {};
|
||||||
|
GetLocalTime(&st);
|
||||||
|
char line[1200] = {};
|
||||||
|
_snprintf_s(
|
||||||
|
line,
|
||||||
|
sizeof(line),
|
||||||
|
_TRUNCATE,
|
||||||
|
"[%04u-%02u-%02u %02u:%02u:%02u] %s\r\n",
|
||||||
|
static_cast<unsigned>(st.wYear),
|
||||||
|
static_cast<unsigned>(st.wMonth),
|
||||||
|
static_cast<unsigned>(st.wDay),
|
||||||
|
static_cast<unsigned>(st.wHour),
|
||||||
|
static_cast<unsigned>(st.wMinute),
|
||||||
|
static_cast<unsigned>(st.wSecond),
|
||||||
|
message
|
||||||
|
);
|
||||||
|
stream << line;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_dpi_awareness() {
|
||||||
|
using SetProcessDpiAwarenessContextFn = BOOL(WINAPI*)(HANDLE);
|
||||||
|
auto user32 = GetModuleHandleW(L"user32.dll");
|
||||||
|
if (!user32) {
|
||||||
|
user32 = LoadLibraryW(L"user32.dll");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user32) {
|
||||||
|
auto set_context = reinterpret_cast<SetProcessDpiAwarenessContextFn>(
|
||||||
|
GetProcAddress(user32, "SetProcessDpiAwarenessContext")
|
||||||
|
);
|
||||||
|
if (set_context && set_context(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) {
|
||||||
|
helper_logf("[cgss-borderless-helper] enabled per-monitor-v2 DPI awareness");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SetProcessDPIAware();
|
||||||
|
helper_logf("[cgss-borderless-helper] enabled legacy DPI awareness");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool parse_arguments() {
|
||||||
|
LPWSTR* argv = nullptr;
|
||||||
|
int argc = 0;
|
||||||
|
argv = CommandLineToArgvW(GetCommandLineW(), &argc);
|
||||||
|
if (!argv) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ok = false;
|
||||||
|
for (int i = 1; i + 1 < argc; ++i) {
|
||||||
|
if (lstrcmpiW(argv[i], L"--pid") == 0) {
|
||||||
|
g_target_process_id = wcstoul(argv[i + 1], nullptr, 10);
|
||||||
|
ok = g_target_process_id != 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalFree(argv);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WindowSearchContext {
|
||||||
|
DWORD process_id = 0;
|
||||||
|
HWND hwnd = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
BOOL CALLBACK enum_windows_proc(HWND hwnd, LPARAM lparam) {
|
||||||
|
auto* context = reinterpret_cast<WindowSearchContext*>(lparam);
|
||||||
|
if (!context) {
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD process_id = 0;
|
||||||
|
GetWindowThreadProcessId(hwnd, &process_id);
|
||||||
|
if (process_id != context->process_id) {
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
if (GetWindow(hwnd, GW_OWNER) != nullptr) {
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
LONG_PTR style = GetWindowLongPtrW(hwnd, GWL_STYLE);
|
||||||
|
if ((style & WS_CHILD) != 0) {
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
context->hwnd = hwnd;
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
HWND find_main_window(DWORD process_id) {
|
||||||
|
WindowSearchContext context;
|
||||||
|
context.process_id = process_id;
|
||||||
|
EnumWindows(&enum_windows_proc, reinterpret_cast<LPARAM>(&context));
|
||||||
|
return context.hwnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get_monitor_rect(HWND hwnd, RECT& rect, MONITORINFOEXW* out_monitor_info = nullptr) {
|
||||||
|
MONITORINFOEXW monitor_info = {};
|
||||||
|
monitor_info.cbSize = sizeof(monitor_info);
|
||||||
|
HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
|
||||||
|
if (!monitor || !GetMonitorInfoW(monitor, &monitor_info)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
rect = monitor_info.rcMonitor;
|
||||||
|
if (out_monitor_info) {
|
||||||
|
*out_monitor_info = monitor_info;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void save_window_state(HWND hwnd) {
|
||||||
|
if (g_saved_state_valid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GetWindowRect(hwnd, &g_saved_window_rect);
|
||||||
|
g_saved_style = GetWindowLongPtrW(hwnd, GWL_STYLE);
|
||||||
|
g_saved_exstyle = GetWindowLongPtrW(hwnd, GWL_EXSTYLE);
|
||||||
|
g_saved_state_valid = true;
|
||||||
|
helper_logf(
|
||||||
|
"[cgss-borderless-helper] saved window rect=(%ld,%ld)-(%ld,%ld)",
|
||||||
|
static_cast<long>(g_saved_window_rect.left),
|
||||||
|
static_cast<long>(g_saved_window_rect.top),
|
||||||
|
static_cast<long>(g_saved_window_rect.right),
|
||||||
|
static_cast<long>(g_saved_window_rect.bottom)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void apply_borderless_once(HWND hwnd) {
|
||||||
|
RECT monitor_rect = {};
|
||||||
|
MONITORINFOEXW monitor_info = {};
|
||||||
|
if (!get_monitor_rect(hwnd, monitor_rect, &monitor_info)) {
|
||||||
|
helper_logf("[cgss-borderless-helper] failed to query monitor rect");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
save_window_state(hwnd);
|
||||||
|
|
||||||
|
LONG_PTR style = GetWindowLongPtrW(hwnd, GWL_STYLE);
|
||||||
|
LONG_PTR exstyle = GetWindowLongPtrW(hwnd, GWL_EXSTYLE);
|
||||||
|
LONG_PTR new_style = style & ~(WS_CAPTION | WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX);
|
||||||
|
LONG_PTR new_exstyle = exstyle &
|
||||||
|
~(WS_EX_DLGMODALFRAME | WS_EX_COMPOSITED | WS_EX_WINDOWEDGE |
|
||||||
|
WS_EX_CLIENTEDGE | WS_EX_LAYERED | WS_EX_STATICEDGE |
|
||||||
|
WS_EX_TOOLWINDOW | WS_EX_APPWINDOW);
|
||||||
|
|
||||||
|
SetWindowPos(
|
||||||
|
hwnd,
|
||||||
|
nullptr,
|
||||||
|
monitor_rect.left,
|
||||||
|
monitor_rect.top,
|
||||||
|
monitor_rect.right - monitor_rect.left,
|
||||||
|
monitor_rect.bottom - monitor_rect.top,
|
||||||
|
SWP_SHOWWINDOW | SWP_NOOWNERZORDER | SWP_NOSENDCHANGING
|
||||||
|
);
|
||||||
|
|
||||||
|
Sleep(kDelayedStyleApplyMs);
|
||||||
|
SetWindowLongPtrW(hwnd, GWL_STYLE, new_style);
|
||||||
|
SetWindowLongPtrW(hwnd, GWL_EXSTYLE, new_exstyle);
|
||||||
|
SetWindowPos(
|
||||||
|
hwnd,
|
||||||
|
nullptr,
|
||||||
|
monitor_rect.left,
|
||||||
|
monitor_rect.top,
|
||||||
|
monitor_rect.right - monitor_rect.left,
|
||||||
|
monitor_rect.bottom - monitor_rect.top,
|
||||||
|
SWP_FRAMECHANGED | SWP_SHOWWINDOW | SWP_NOOWNERZORDER | SWP_NOSENDCHANGING
|
||||||
|
);
|
||||||
|
|
||||||
|
RECT actual_rect = {};
|
||||||
|
GetWindowRect(hwnd, &actual_rect);
|
||||||
|
helper_logf(
|
||||||
|
"[cgss-borderless-helper] applied borderless class=%ls monitor=%ls target=(%ld,%ld)-(%ld,%ld) actual=(%ld,%ld)-(%ld,%ld)",
|
||||||
|
get_window_class_name(hwnd).c_str(),
|
||||||
|
monitor_info.szDevice,
|
||||||
|
static_cast<long>(monitor_rect.left),
|
||||||
|
static_cast<long>(monitor_rect.top),
|
||||||
|
static_cast<long>(monitor_rect.right),
|
||||||
|
static_cast<long>(monitor_rect.bottom),
|
||||||
|
static_cast<long>(actual_rect.left),
|
||||||
|
static_cast<long>(actual_rect.top),
|
||||||
|
static_cast<long>(actual_rect.right),
|
||||||
|
static_cast<long>(actual_rect.bottom)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool needs_borderless_reapply(HWND hwnd) {
|
||||||
|
RECT expected_rect = {};
|
||||||
|
if (!get_monitor_rect(hwnd, expected_rect)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RECT current_rect = {};
|
||||||
|
if (!GetWindowRect(hwnd, ¤t_rect)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LONG_PTR style = GetWindowLongPtrW(hwnd, GWL_STYLE);
|
||||||
|
const bool has_border = (style & (WS_CAPTION | WS_THICKFRAME | WS_SYSMENU)) != 0;
|
||||||
|
const bool rect_mismatch = current_rect.left != expected_rect.left ||
|
||||||
|
current_rect.top != expected_rect.top ||
|
||||||
|
current_rect.right != expected_rect.right ||
|
||||||
|
current_rect.bottom != expected_rect.bottom;
|
||||||
|
return has_border || rect_mismatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
void restore_window(HWND hwnd) {
|
||||||
|
if (!g_saved_state_valid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetWindowLongPtrW(hwnd, GWL_STYLE, g_saved_style);
|
||||||
|
SetWindowLongPtrW(hwnd, GWL_EXSTYLE, g_saved_exstyle);
|
||||||
|
SetWindowPos(
|
||||||
|
hwnd,
|
||||||
|
nullptr,
|
||||||
|
g_saved_window_rect.left,
|
||||||
|
g_saved_window_rect.top,
|
||||||
|
g_saved_window_rect.right - g_saved_window_rect.left,
|
||||||
|
g_saved_window_rect.bottom - g_saved_window_rect.top,
|
||||||
|
SWP_FRAMECHANGED | SWP_SHOWWINDOW | SWP_NOOWNERZORDER | SWP_NOSENDCHANGING
|
||||||
|
);
|
||||||
|
helper_logf("[cgss-borderless-helper] restored original window style and rect");
|
||||||
|
}
|
||||||
|
|
||||||
|
void toggle_borderless(HWND hwnd) {
|
||||||
|
if (!hwnd) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RECT monitor_rect = {};
|
||||||
|
MONITORINFOEXW monitor_info = {};
|
||||||
|
if (!get_monitor_rect(hwnd, monitor_rect, &monitor_info)) {
|
||||||
|
helper_logf("[cgss-borderless-helper] failed to resolve monitor rect for toggle");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_borderless_active) {
|
||||||
|
g_borderless_active = false;
|
||||||
|
restore_window(hwnd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_borderless_active = true;
|
||||||
|
helper_logf(
|
||||||
|
"[cgss-borderless-helper] borderless enabled monitor=%ls rect=(%ld,%ld)-(%ld,%ld)",
|
||||||
|
monitor_info.szDevice,
|
||||||
|
static_cast<long>(monitor_rect.left),
|
||||||
|
static_cast<long>(monitor_rect.top),
|
||||||
|
static_cast<long>(monitor_rect.right),
|
||||||
|
static_cast<long>(monitor_rect.bottom)
|
||||||
|
);
|
||||||
|
apply_borderless_once(hwnd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
|
||||||
|
set_dpi_awareness();
|
||||||
|
|
||||||
|
if (!parse_arguments()) {
|
||||||
|
helper_logf("[cgss-borderless-helper] missing or invalid --pid argument");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
HANDLE target_process = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION, FALSE, g_target_process_id);
|
||||||
|
if (!target_process) {
|
||||||
|
helper_logf("[cgss-borderless-helper] failed to open target process");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < kWindowPollMaxAttempts; ++i) {
|
||||||
|
g_main_window = find_main_window(g_target_process_id);
|
||||||
|
if (g_main_window) {
|
||||||
|
helper_logf("[cgss-borderless-helper] found main window hwnd=0x%p", g_main_window);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (WaitForSingleObject(target_process, 0) == WAIT_OBJECT_0) {
|
||||||
|
CloseHandle(target_process);
|
||||||
|
helper_logf("[cgss-borderless-helper] target process exited before main window appeared");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
Sleep(kWindowPollIntervalMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!g_main_window) {
|
||||||
|
CloseHandle(target_process);
|
||||||
|
helper_logf("[cgss-borderless-helper] timed out waiting for main window");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
SHORT previous_hotkey_state = 0;
|
||||||
|
DWORD last_maintain_tick = GetTickCount();
|
||||||
|
|
||||||
|
while (WaitForSingleObject(target_process, 0) != WAIT_OBJECT_0) {
|
||||||
|
if (!IsWindow(g_main_window)) {
|
||||||
|
helper_logf("[cgss-borderless-helper] target window no longer valid");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
SHORT current_hotkey_state = GetAsyncKeyState(kToggleKey);
|
||||||
|
bool is_pressed = (current_hotkey_state & 0x8000) != 0;
|
||||||
|
bool was_pressed = (previous_hotkey_state & 0x8000) != 0;
|
||||||
|
if (is_pressed && !was_pressed) {
|
||||||
|
helper_logf("[cgss-borderless-helper] F11 pressed");
|
||||||
|
toggle_borderless(g_main_window);
|
||||||
|
}
|
||||||
|
previous_hotkey_state = current_hotkey_state;
|
||||||
|
|
||||||
|
DWORD now = GetTickCount();
|
||||||
|
if (g_borderless_active && now - last_maintain_tick >= kMaintainIntervalMs) {
|
||||||
|
if (needs_borderless_reapply(g_main_window)) {
|
||||||
|
helper_logf("[cgss-borderless-helper] detected borderless drift, reapplying");
|
||||||
|
apply_borderless_once(g_main_window);
|
||||||
|
}
|
||||||
|
last_maintain_tick = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
Sleep(kHotkeyPollIntervalMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseHandle(target_process);
|
||||||
|
helper_logf("[cgss-borderless-helper] target process exited, helper shutting down");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
+91
-179
@@ -1,6 +1,7 @@
|
|||||||
#include "stdinclude.hpp"
|
#include "stdinclude.hpp"
|
||||||
|
|
||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
|
#include "hook.hpp"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
using GetSchemeTypeFn = int (*)(const MethodInfo*);
|
using GetSchemeTypeFn = int (*)(const MethodInfo*);
|
||||||
@@ -400,187 +401,98 @@ namespace {
|
|||||||
hook_log("[cgss-dmm-hook] force_http disabled, keeping original scheme");
|
hook_log("[cgss-dmm-hook] force_http disabled, keeping original scheme");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (get_application_server_url_addr) {
|
auto hook_method = [](uintptr_t address, void* detour, void** original, const char* name) {
|
||||||
auto create_status = MH_CreateHook(
|
if (!address) {
|
||||||
reinterpret_cast<void*>(get_application_server_url_addr),
|
return;
|
||||||
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-dmm-hook] hooked Stage.NetworkUtil.GetApplicationServerUrl");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
auto create_status = MH_CreateHook(reinterpret_cast<void*>(address), detour, original);
|
||||||
|
if (create_status != MH_OK && create_status != MH_ERROR_ALREADY_CREATED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto enable_status = MH_EnableHook(reinterpret_cast<void*>(address));
|
||||||
|
if (enable_status == MH_OK || enable_status == MH_ERROR_ENABLED) {
|
||||||
|
hook_logf("[cgss-dmm-hook] hooked %s", name);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (get_resource_server_url_addr) {
|
hook_method(
|
||||||
auto create_status = MH_CreateHook(
|
get_application_server_url_addr,
|
||||||
reinterpret_cast<void*>(get_resource_server_url_addr),
|
reinterpret_cast<void*>(&get_application_server_url_hook),
|
||||||
reinterpret_cast<void*>(&get_resource_server_url_hook),
|
reinterpret_cast<void**>(&g_orig_get_application_server_url),
|
||||||
reinterpret_cast<void**>(&g_orig_get_resource_server_url)
|
"Stage.NetworkUtil.GetApplicationServerUrl"
|
||||||
);
|
);
|
||||||
if (create_status == MH_OK || create_status == MH_ERROR_ALREADY_CREATED) {
|
hook_method(
|
||||||
auto enable_status = MH_EnableHook(reinterpret_cast<void*>(get_resource_server_url_addr));
|
get_resource_server_url_addr,
|
||||||
if (enable_status == MH_OK || enable_status == MH_ERROR_ENABLED) {
|
reinterpret_cast<void*>(&get_resource_server_url_hook),
|
||||||
hook_log("[cgss-dmm-hook] hooked Stage.NetworkUtil.GetResourceServerUrl");
|
reinterpret_cast<void**>(&g_orig_get_resource_server_url),
|
||||||
}
|
"Stage.NetworkUtil.GetResourceServerUrl"
|
||||||
}
|
);
|
||||||
}
|
hook_method(
|
||||||
|
get_tele_scope_server_url_addr,
|
||||||
if (get_tele_scope_server_url_addr) {
|
reinterpret_cast<void*>(&get_tele_scope_server_url_hook),
|
||||||
auto create_status = MH_CreateHook(
|
reinterpret_cast<void**>(&g_orig_get_tele_scope_server_url),
|
||||||
reinterpret_cast<void*>(get_tele_scope_server_url_addr),
|
"Stage.NetworkUtil.GetTeleScopeServerUrl"
|
||||||
reinterpret_cast<void*>(&get_tele_scope_server_url_hook),
|
);
|
||||||
reinterpret_cast<void**>(&g_orig_get_tele_scope_server_url)
|
hook_method(
|
||||||
);
|
get_concert_server_url_addr,
|
||||||
if (create_status == MH_OK || create_status == MH_ERROR_ALREADY_CREATED) {
|
reinterpret_cast<void*>(&get_concert_server_url_hook),
|
||||||
auto enable_status = MH_EnableHook(reinterpret_cast<void*>(get_tele_scope_server_url_addr));
|
reinterpret_cast<void**>(&g_orig_get_concert_server_url),
|
||||||
if (enable_status == MH_OK || enable_status == MH_ERROR_ENABLED) {
|
"Stage.NetworkUtil.GetConcertServerUrl"
|
||||||
hook_log("[cgss-dmm-hook] hooked Stage.NetworkUtil.GetTeleScopeServerUrl");
|
);
|
||||||
}
|
hook_method(
|
||||||
}
|
cp_get_application_server_url_addr,
|
||||||
}
|
reinterpret_cast<void*>(&custom_preference_get_application_server_url_hook),
|
||||||
|
reinterpret_cast<void**>(&g_orig_custom_preference_get_application_server_url),
|
||||||
if (get_concert_server_url_addr) {
|
"Cute.CustomPreference.GetApplicationServerURL"
|
||||||
auto create_status = MH_CreateHook(
|
);
|
||||||
reinterpret_cast<void*>(get_concert_server_url_addr),
|
hook_method(
|
||||||
reinterpret_cast<void*>(&get_concert_server_url_hook),
|
cp_get_tele_scope_server_url_addr,
|
||||||
reinterpret_cast<void**>(&g_orig_get_concert_server_url)
|
reinterpret_cast<void*>(&custom_preference_get_tele_scope_server_url_hook),
|
||||||
);
|
reinterpret_cast<void**>(&g_orig_custom_preference_get_tele_scope_server_url),
|
||||||
if (create_status == MH_OK || create_status == MH_ERROR_ALREADY_CREATED) {
|
"Cute.CustomPreference.GetTeleScopeServerURL"
|
||||||
auto enable_status = MH_EnableHook(reinterpret_cast<void*>(get_concert_server_url_addr));
|
);
|
||||||
if (enable_status == MH_OK || enable_status == MH_ERROR_ENABLED) {
|
hook_method(
|
||||||
hook_log("[cgss-dmm-hook] hooked Stage.NetworkUtil.GetConcertServerUrl");
|
cp_get_concert_server_url_addr,
|
||||||
}
|
reinterpret_cast<void*>(&custom_preference_get_concert_server_url_hook),
|
||||||
}
|
reinterpret_cast<void**>(&g_orig_custom_preference_get_concert_server_url),
|
||||||
}
|
"Cute.CustomPreference.GetConcertServerURL"
|
||||||
|
);
|
||||||
if (cp_get_application_server_url_addr) {
|
hook_method(
|
||||||
auto create_status = MH_CreateHook(
|
cp_get_resource_server_url_addr,
|
||||||
reinterpret_cast<void*>(cp_get_application_server_url_addr),
|
reinterpret_cast<void*>(&custom_preference_get_resource_server_url_hook),
|
||||||
reinterpret_cast<void*>(&custom_preference_get_application_server_url_hook),
|
reinterpret_cast<void**>(&g_orig_custom_preference_get_resource_server_url),
|
||||||
reinterpret_cast<void**>(&g_orig_custom_preference_get_application_server_url)
|
"Cute.CustomPreference.GetResourceServerURL"
|
||||||
);
|
);
|
||||||
if (create_status == MH_OK || create_status == MH_ERROR_ALREADY_CREATED) {
|
hook_method(
|
||||||
auto enable_status = MH_EnableHook(reinterpret_cast<void*>(cp_get_application_server_url_addr));
|
cp_set_application_server_url_addr,
|
||||||
if (enable_status == MH_OK || enable_status == MH_ERROR_ENABLED) {
|
reinterpret_cast<void*>(&custom_preference_set_application_server_url_hook),
|
||||||
hook_log("[cgss-dmm-hook] hooked Cute.CustomPreference.GetApplicationServerURL");
|
reinterpret_cast<void**>(&g_orig_custom_preference_set_application_server_url),
|
||||||
}
|
"Cute.CustomPreference.SetApplicationServerURL"
|
||||||
}
|
);
|
||||||
}
|
hook_method(
|
||||||
|
cp_set_tele_scope_server_url_addr,
|
||||||
if (cp_get_tele_scope_server_url_addr) {
|
reinterpret_cast<void*>(&custom_preference_set_tele_scope_server_url_hook),
|
||||||
auto create_status = MH_CreateHook(
|
reinterpret_cast<void**>(&g_orig_custom_preference_set_tele_scope_server_url),
|
||||||
reinterpret_cast<void*>(cp_get_tele_scope_server_url_addr),
|
"Cute.CustomPreference.SetTeleScopeServerURL"
|
||||||
reinterpret_cast<void*>(&custom_preference_get_tele_scope_server_url_hook),
|
);
|
||||||
reinterpret_cast<void**>(&g_orig_custom_preference_get_tele_scope_server_url)
|
hook_method(
|
||||||
);
|
cp_set_concert_server_url_addr,
|
||||||
if (create_status == MH_OK || create_status == MH_ERROR_ALREADY_CREATED) {
|
reinterpret_cast<void*>(&custom_preference_set_concert_server_url_hook),
|
||||||
auto enable_status = MH_EnableHook(reinterpret_cast<void*>(cp_get_tele_scope_server_url_addr));
|
reinterpret_cast<void**>(&g_orig_custom_preference_set_concert_server_url),
|
||||||
if (enable_status == MH_OK || enable_status == MH_ERROR_ENABLED) {
|
"Cute.CustomPreference.SetConcertServerURL"
|
||||||
hook_log("[cgss-dmm-hook] hooked Cute.CustomPreference.GetTeleScopeServerURL");
|
);
|
||||||
}
|
hook_method(
|
||||||
}
|
cp_set_resource_server_url_addr,
|
||||||
}
|
reinterpret_cast<void*>(&custom_preference_set_resource_server_url_hook),
|
||||||
|
reinterpret_cast<void**>(&g_orig_custom_preference_set_resource_server_url),
|
||||||
if (cp_get_concert_server_url_addr) {
|
"Cute.CustomPreference.SetResourceServerURL"
|
||||||
auto create_status = MH_CreateHook(
|
);
|
||||||
reinterpret_cast<void*>(cp_get_concert_server_url_addr),
|
hook_method(
|
||||||
reinterpret_cast<void*>(&custom_preference_get_concert_server_url_hook),
|
cp_set_scheme_mode_addr,
|
||||||
reinterpret_cast<void**>(&g_orig_custom_preference_get_concert_server_url)
|
reinterpret_cast<void*>(&custom_preference_set_scheme_mode_hook),
|
||||||
);
|
reinterpret_cast<void**>(&g_orig_custom_preference_set_scheme_mode),
|
||||||
if (create_status == MH_OK || create_status == MH_ERROR_ALREADY_CREATED) {
|
"Cute.CustomPreference.SetScemeMode"
|
||||||
auto enable_status = MH_EnableHook(reinterpret_cast<void*>(cp_get_concert_server_url_addr));
|
);
|
||||||
if (enable_status == MH_OK || enable_status == MH_ERROR_ENABLED) {
|
|
||||||
hook_log("[cgss-dmm-hook] hooked Cute.CustomPreference.GetConcertServerURL");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cp_get_resource_server_url_addr) {
|
|
||||||
auto create_status = MH_CreateHook(
|
|
||||||
reinterpret_cast<void*>(cp_get_resource_server_url_addr),
|
|
||||||
reinterpret_cast<void*>(&custom_preference_get_resource_server_url_hook),
|
|
||||||
reinterpret_cast<void**>(&g_orig_custom_preference_get_resource_server_url)
|
|
||||||
);
|
|
||||||
if (create_status == MH_OK || create_status == MH_ERROR_ALREADY_CREATED) {
|
|
||||||
auto enable_status = MH_EnableHook(reinterpret_cast<void*>(cp_get_resource_server_url_addr));
|
|
||||||
if (enable_status == MH_OK || enable_status == MH_ERROR_ENABLED) {
|
|
||||||
hook_log("[cgss-dmm-hook] hooked Cute.CustomPreference.GetResourceServerURL");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cp_set_application_server_url_addr) {
|
|
||||||
auto create_status = MH_CreateHook(
|
|
||||||
reinterpret_cast<void*>(cp_set_application_server_url_addr),
|
|
||||||
reinterpret_cast<void*>(&custom_preference_set_application_server_url_hook),
|
|
||||||
reinterpret_cast<void**>(&g_orig_custom_preference_set_application_server_url)
|
|
||||||
);
|
|
||||||
if (create_status == MH_OK || create_status == MH_ERROR_ALREADY_CREATED) {
|
|
||||||
auto enable_status = MH_EnableHook(reinterpret_cast<void*>(cp_set_application_server_url_addr));
|
|
||||||
if (enable_status == MH_OK || enable_status == MH_ERROR_ENABLED) {
|
|
||||||
hook_log("[cgss-dmm-hook] hooked Cute.CustomPreference.SetApplicationServerURL");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cp_set_tele_scope_server_url_addr) {
|
|
||||||
auto create_status = MH_CreateHook(
|
|
||||||
reinterpret_cast<void*>(cp_set_tele_scope_server_url_addr),
|
|
||||||
reinterpret_cast<void*>(&custom_preference_set_tele_scope_server_url_hook),
|
|
||||||
reinterpret_cast<void**>(&g_orig_custom_preference_set_tele_scope_server_url)
|
|
||||||
);
|
|
||||||
if (create_status == MH_OK || create_status == MH_ERROR_ALREADY_CREATED) {
|
|
||||||
auto enable_status = MH_EnableHook(reinterpret_cast<void*>(cp_set_tele_scope_server_url_addr));
|
|
||||||
if (enable_status == MH_OK || enable_status == MH_ERROR_ENABLED) {
|
|
||||||
hook_log("[cgss-dmm-hook] hooked Cute.CustomPreference.SetTeleScopeServerURL");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cp_set_concert_server_url_addr) {
|
|
||||||
auto create_status = MH_CreateHook(
|
|
||||||
reinterpret_cast<void*>(cp_set_concert_server_url_addr),
|
|
||||||
reinterpret_cast<void*>(&custom_preference_set_concert_server_url_hook),
|
|
||||||
reinterpret_cast<void**>(&g_orig_custom_preference_set_concert_server_url)
|
|
||||||
);
|
|
||||||
if (create_status == MH_OK || create_status == MH_ERROR_ALREADY_CREATED) {
|
|
||||||
auto enable_status = MH_EnableHook(reinterpret_cast<void*>(cp_set_concert_server_url_addr));
|
|
||||||
if (enable_status == MH_OK || enable_status == MH_ERROR_ENABLED) {
|
|
||||||
hook_log("[cgss-dmm-hook] hooked Cute.CustomPreference.SetConcertServerURL");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cp_set_resource_server_url_addr) {
|
|
||||||
auto create_status = MH_CreateHook(
|
|
||||||
reinterpret_cast<void*>(cp_set_resource_server_url_addr),
|
|
||||||
reinterpret_cast<void*>(&custom_preference_set_resource_server_url_hook),
|
|
||||||
reinterpret_cast<void**>(&g_orig_custom_preference_set_resource_server_url)
|
|
||||||
);
|
|
||||||
if (create_status == MH_OK || create_status == MH_ERROR_ALREADY_CREATED) {
|
|
||||||
auto enable_status = MH_EnableHook(reinterpret_cast<void*>(cp_set_resource_server_url_addr));
|
|
||||||
if (enable_status == MH_OK || enable_status == MH_ERROR_ENABLED) {
|
|
||||||
hook_log("[cgss-dmm-hook] hooked Cute.CustomPreference.SetResourceServerURL");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cp_set_scheme_mode_addr) {
|
|
||||||
auto create_status = MH_CreateHook(
|
|
||||||
reinterpret_cast<void*>(cp_set_scheme_mode_addr),
|
|
||||||
reinterpret_cast<void*>(&custom_preference_set_scheme_mode_hook),
|
|
||||||
reinterpret_cast<void**>(&g_orig_custom_preference_set_scheme_mode)
|
|
||||||
);
|
|
||||||
if (create_status == MH_OK || create_status == MH_ERROR_ALREADY_CREATED) {
|
|
||||||
auto enable_status = MH_EnableHook(reinterpret_cast<void*>(cp_set_scheme_mode_addr));
|
|
||||||
if (enable_status == MH_OK || enable_status == MH_ERROR_ENABLED) {
|
|
||||||
hook_log("[cgss-dmm-hook] hooked Cute.CustomPreference.SetScemeMode");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
apply_custom_preference_url_overrides();
|
apply_custom_preference_url_overrides();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
void start_hook_thread();
|
||||||
+102
-2
@@ -1,5 +1,8 @@
|
|||||||
#include "stdinclude.hpp"
|
#include "stdinclude.hpp"
|
||||||
|
|
||||||
|
#include "config.hpp"
|
||||||
|
#include "hook.hpp"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
void* GetFileVersionInfoA_Original = nullptr;
|
void* GetFileVersionInfoA_Original = nullptr;
|
||||||
void* GetFileVersionInfoSizeA_Original = nullptr;
|
void* GetFileVersionInfoSizeA_Original = nullptr;
|
||||||
@@ -33,11 +36,45 @@ extern "C" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void start_hook_thread();
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr DWORD kProxyInitDelayMs = 1000;
|
constexpr DWORD kProxyInitDelayMs = 1000;
|
||||||
|
constexpr const char* kHelperExeName = "cgss-borderless-helper.exe";
|
||||||
|
constexpr const char* kGameExeName = "imascgstage.exe";
|
||||||
HMODULE g_original = nullptr;
|
HMODULE g_original = nullptr;
|
||||||
|
volatile LONG g_helper_started = 0;
|
||||||
|
|
||||||
|
bool is_game_process() {
|
||||||
|
char path[MAX_PATH] = {};
|
||||||
|
DWORD len = GetModuleFileNameA(nullptr, path, MAX_PATH);
|
||||||
|
if (len == 0 || len >= MAX_PATH) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* base_name = path;
|
||||||
|
for (const char* p = path; *p != '\0'; ++p) {
|
||||||
|
if (*p == '\\' || *p == '/') {
|
||||||
|
base_name = p + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lstrcmpiA(base_name, kGameExeName) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_game_directory() {
|
||||||
|
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';
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
void load_original_version_dll() {
|
void load_original_version_dll() {
|
||||||
if (g_original) {
|
if (g_original) {
|
||||||
@@ -68,9 +105,72 @@ namespace {
|
|||||||
hook_log("[cgss-dmm-hook] version.dll initialized");
|
hook_log("[cgss-dmm-hook] version.dll initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void start_borderless_helper() {
|
||||||
|
if (!config::get().launch_borderless_helper) {
|
||||||
|
hook_log("[cgss-dmm-hook] launch_borderless_helper=false, skipping helper launch");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (InterlockedCompareExchange(&g_helper_started, 1, 0) != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto game_dir = get_game_directory();
|
||||||
|
if (game_dir.empty()) {
|
||||||
|
hook_log("[cgss-dmm-hook] failed to resolve game directory for helper launch");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string helper_path = game_dir;
|
||||||
|
helper_path += "\\";
|
||||||
|
helper_path += kHelperExeName;
|
||||||
|
|
||||||
|
DWORD attributes = GetFileAttributesA(helper_path.c_str());
|
||||||
|
if (attributes == INVALID_FILE_ATTRIBUTES || (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
|
||||||
|
hook_log("[cgss-dmm-hook] borderless helper exe not found, skipping launch");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char command_line[512] = {};
|
||||||
|
wsprintfA(command_line, "\"%s\" --pid %lu", helper_path.c_str(), GetCurrentProcessId());
|
||||||
|
|
||||||
|
STARTUPINFOA startup_info = {};
|
||||||
|
startup_info.cb = sizeof(startup_info);
|
||||||
|
PROCESS_INFORMATION process_info = {};
|
||||||
|
BOOL result = CreateProcessA(
|
||||||
|
helper_path.c_str(),
|
||||||
|
command_line,
|
||||||
|
nullptr,
|
||||||
|
nullptr,
|
||||||
|
FALSE,
|
||||||
|
CREATE_NO_WINDOW,
|
||||||
|
nullptr,
|
||||||
|
game_dir.c_str(),
|
||||||
|
&startup_info,
|
||||||
|
&process_info
|
||||||
|
);
|
||||||
|
if (!result) {
|
||||||
|
hook_logf(
|
||||||
|
"[cgss-dmm-hook] failed to launch borderless helper, error=%lu",
|
||||||
|
static_cast<unsigned long>(GetLastError())
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseHandle(process_info.hThread);
|
||||||
|
CloseHandle(process_info.hProcess);
|
||||||
|
hook_log("[cgss-dmm-hook] launched borderless helper");
|
||||||
|
}
|
||||||
|
|
||||||
DWORD WINAPI init_thread(void*) {
|
DWORD WINAPI init_thread(void*) {
|
||||||
Sleep(kProxyInitDelayMs);
|
Sleep(kProxyInitDelayMs);
|
||||||
|
if (!is_game_process()) {
|
||||||
|
load_original_version_dll();
|
||||||
|
hook_log("[cgss-dmm-hook] non-game process detected, skipping hook/helper startup");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
config::load();
|
||||||
load_original_version_dll();
|
load_original_version_dll();
|
||||||
|
start_borderless_helper();
|
||||||
start_hook_thread();
|
start_hook_thread();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user