小助手内嵌 GM 页:网页重定向 + 物品分页 + 装备强化增幅发放
- 90CN.dll 新增 IE 控件接管,把腾讯小助手页面重定向到本地 GM 页 - gmweb:物品列表改分页(每页 10/20/50,列表固定 6 行高) - 发放走 grant-options,支持强化 0-31、增幅属性+等级、锻造 0-8、品质 - 服务端补齐 amplify_level:等级统一写入 ext_data0/packed_flag_byte/reinforce/upgrade_level - 增幅与强化互斥校验,避免写出客户端无法创建的装备
This commit is contained in:
1 parent
6e570fe820
commit
18e98ca7c9
13 files changed
+1206
-11
No files matched your search
@@ -0,0 +1,292 @@
|
||||
// ============================================================================
|
||||
// 90CN_web_redirect.cpp -- 把客户端内嵌网页窗口跳到本地 GM 页面
|
||||
//
|
||||
// 背景:DNF 客户端里那个「小井盖 / 小助手」窗口是 IE 内核(窗口类链为
|
||||
// Browser Example -> Shell DocObject View -> Internet Explorer_Server),
|
||||
// 地址在运行时拼接,客户端二进制与 PVF 里都没有明文域名,硬改字符串
|
||||
// 不可行。所以这里不碰原地址,而是等控件创建出来后,直接把文档导航
|
||||
// 到我们自己的页面。
|
||||
//
|
||||
// 做法:起一个后台线程轮询本进程内的 Internet Explorer_Server 窗口;一旦
|
||||
// 发现新窗口,等原生导航落定,再用 WM_HTML_GETOBJECT + ObjectFromLresult
|
||||
// 取到 IHTMLDocument2,经 IHTMLWindow2::execScript 执行
|
||||
// location.replace(...) 完成跳转。同一窗口只跳一次(force=1 时若发现
|
||||
// 文档又跑到别的域名,会再次拉回)。
|
||||
//
|
||||
// 配置:DLL 同目录的 90CN_web.ini
|
||||
// [web]
|
||||
// enabled=1
|
||||
// url=http://127.0.0.1:18112/
|
||||
// force=0
|
||||
// delay=1200
|
||||
// 没有配置文件时用上面的默认值(enabled=1)。
|
||||
//
|
||||
// 失败不影响游戏:任何一步取不到对象都只写日志,不会终止进程。
|
||||
// ============================================================================
|
||||
|
||||
#include <windows.h>
|
||||
#include <ole2.h>
|
||||
#include <oleacc.h>
|
||||
#include <mshtml.h>
|
||||
#include <string>
|
||||
|
||||
// 90CN.cpp 里的 LogLine 在匿名 namespace 内(内部链接),所以走它导出的
|
||||
// C 链接转发口,保证本模块的日志和安装流程写进同一个 90CN_trace.log。
|
||||
extern "C" void __cdecl CnmLogLine(const char* format, ...);
|
||||
#define LogLine CnmLogLine
|
||||
|
||||
namespace {
|
||||
|
||||
const wchar_t kIniName[] = L"90CN_web.ini";
|
||||
const wchar_t kIeServerClass[] = L"Internet Explorer_Server";
|
||||
|
||||
struct WebConfig {
|
||||
bool enabled;
|
||||
bool force;
|
||||
int delayMs;
|
||||
std::wstring url;
|
||||
};
|
||||
|
||||
WebConfig g_cfg = { true, false, 1200, L"http://127.0.0.1:18112/" };
|
||||
|
||||
DWORD g_threadId = 0;
|
||||
volatile LONG g_stop = 0;
|
||||
|
||||
std::wstring WideFromAnsi(const char* s)
|
||||
{
|
||||
if (!s || !*s) return std::wstring();
|
||||
int need = MultiByteToWideChar(CP_UTF8, 0, s, -1, nullptr, 0);
|
||||
if (need <= 1) {
|
||||
need = MultiByteToWideChar(CP_ACP, 0, s, -1, nullptr, 0);
|
||||
if (need <= 1) return std::wstring();
|
||||
std::wstring out(static_cast<size_t>(need - 1), L'\0');
|
||||
MultiByteToWideChar(CP_ACP, 0, s, -1, &out[0], need);
|
||||
return out;
|
||||
}
|
||||
std::wstring out(static_cast<size_t>(need - 1), L'\0');
|
||||
MultiByteToWideChar(CP_UTF8, 0, s, -1, &out[0], need);
|
||||
return out;
|
||||
}
|
||||
|
||||
std::wstring ModuleDirectory()
|
||||
{
|
||||
wchar_t path[MAX_PATH] = {};
|
||||
HMODULE self = nullptr;
|
||||
// 用数据地址反查自身模块:函数地址在某些加载方式下解析不到。
|
||||
if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
|
||||
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
|
||||
reinterpret_cast<LPCWSTR>(&g_cfg), &self) ||
|
||||
!self) {
|
||||
return std::wstring();
|
||||
}
|
||||
if (!GetModuleFileNameW(self, path, MAX_PATH)) return std::wstring();
|
||||
std::wstring full(path);
|
||||
const size_t slash = full.find_last_of(L"\\/");
|
||||
return slash == std::wstring::npos ? std::wstring() : full.substr(0, slash + 1);
|
||||
}
|
||||
|
||||
void LoadConfig()
|
||||
{
|
||||
const std::wstring dir = ModuleDirectory();
|
||||
if (dir.empty()) {
|
||||
LogLine("web redirect: module dir unresolved, using built-in defaults");
|
||||
return;
|
||||
}
|
||||
const std::wstring ini = dir + kIniName;
|
||||
if (GetFileAttributesW(ini.c_str()) == INVALID_FILE_ATTRIBUTES) {
|
||||
LogLine("web redirect: %ls not found, defaults enabled=1 url=%ls", ini.c_str(), g_cfg.url.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
const UINT enabled = GetPrivateProfileIntW(L"web", L"enabled", 1, ini.c_str());
|
||||
const UINT force = GetPrivateProfileIntW(L"web", L"force", 0, ini.c_str());
|
||||
const UINT delay = GetPrivateProfileIntW(L"web", L"delay", 1200, ini.c_str());
|
||||
|
||||
wchar_t buf[1024] = {};
|
||||
GetPrivateProfileStringW(L"web", L"url", L"http://127.0.0.1:18112/", buf,
|
||||
static_cast<DWORD>(sizeof(buf) / sizeof(buf[0])), ini.c_str());
|
||||
|
||||
g_cfg.enabled = enabled != 0;
|
||||
g_cfg.force = force != 0;
|
||||
g_cfg.delayMs = delay > 200 ? static_cast<int>(delay) : 200;
|
||||
if (buf[0]) g_cfg.url = buf;
|
||||
|
||||
LogLine("web redirect: config enabled=%d force=%d delay=%d url=%ls",
|
||||
g_cfg.enabled ? 1 : 0, g_cfg.force ? 1 : 0, g_cfg.delayMs, g_cfg.url.c_str());
|
||||
}
|
||||
|
||||
bool IsLocalUrl(const std::wstring& url)
|
||||
{
|
||||
// 只认 "http://127.0.0.1:<port>/" 前缀,用来判断当前文档是否已经在我们的页面上。
|
||||
if (g_cfg.url.empty() || url.size() < g_cfg.url.size()) return false;
|
||||
return _wcsnicmp(url.c_str(), g_cfg.url.c_str(), g_cfg.url.size()) == 0;
|
||||
}
|
||||
|
||||
struct FindContext {
|
||||
DWORD pid;
|
||||
HWND found;
|
||||
};
|
||||
|
||||
BOOL CALLBACK OnChildWindow(HWND hwnd, LPARAM param)
|
||||
{
|
||||
FindContext* ctx = reinterpret_cast<FindContext*>(param);
|
||||
wchar_t cls[128] = {};
|
||||
if (GetClassNameW(hwnd, cls, 128) && _wcsicmp(cls, kIeServerClass) == 0) {
|
||||
DWORD pid = 0;
|
||||
GetWindowThreadProcessId(hwnd, &pid);
|
||||
if (pid == ctx->pid) {
|
||||
ctx->found = hwnd;
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL CALLBACK OnTopWindow(HWND hwnd, LPARAM param)
|
||||
{
|
||||
FindContext* ctx = reinterpret_cast<FindContext*>(param);
|
||||
DWORD pid = 0;
|
||||
GetWindowThreadProcessId(hwnd, &pid);
|
||||
if (pid != ctx->pid) return TRUE;
|
||||
EnumChildWindows(hwnd, OnChildWindow, param);
|
||||
return ctx->found ? FALSE : TRUE;
|
||||
}
|
||||
|
||||
HWND FindIeServerWindow()
|
||||
{
|
||||
FindContext ctx = { GetCurrentProcessId(), nullptr };
|
||||
EnumWindows(OnTopWindow, reinterpret_cast<LPARAM>(&ctx));
|
||||
return ctx.found;
|
||||
}
|
||||
|
||||
// 取文档对象。WM_HTML_GETOBJECT 的返回值经 ObjectFromLresult 还原成接口指针。
|
||||
IHTMLDocument2* GetDocument(HWND hwnd)
|
||||
{
|
||||
static const UINT msg = RegisterWindowMessageW(L"WM_HTML_GETOBJECT");
|
||||
if (!msg) return nullptr;
|
||||
|
||||
DWORD_PTR result = 0;
|
||||
const LRESULT ok = SendMessageTimeoutW(hwnd, msg, 0, 0,
|
||||
SMTO_ABORTIFHUNG | SMTO_BLOCK, 4000, &result);
|
||||
if (!ok || !result) return nullptr;
|
||||
|
||||
IHTMLDocument2* doc = nullptr;
|
||||
if (FAILED(ObjectFromLresult(static_cast<LRESULT>(result), IID_IHTMLDocument2, 0,
|
||||
reinterpret_cast<void**>(&doc)))) {
|
||||
return nullptr;
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
|
||||
std::wstring DocumentUrl(IHTMLDocument2* doc)
|
||||
{
|
||||
BSTR url = nullptr;
|
||||
std::wstring out;
|
||||
if (doc && SUCCEEDED(doc->get_URL(&url)) && url) {
|
||||
out.assign(url, SysStringLen(url));
|
||||
}
|
||||
if (url) SysFreeString(url);
|
||||
return out;
|
||||
}
|
||||
|
||||
bool NavigateDocument(IHTMLDocument2* doc)
|
||||
{
|
||||
IHTMLWindow2* win = nullptr;
|
||||
if (FAILED(doc->get_parentWindow(&win)) || !win) return false;
|
||||
|
||||
// 用 location.replace 而不是 href:不往历史里塞记录,避免用户按返回键回到原页。
|
||||
std::wstring script = L"location.replace('";
|
||||
script += g_cfg.url;
|
||||
script += L"')";
|
||||
|
||||
BSTR code = SysAllocString(script.c_str());
|
||||
BSTR lang = SysAllocString(L"JavaScript");
|
||||
VARIANT ret;
|
||||
VariantInit(&ret);
|
||||
HRESULT hr = win->execScript(code, lang, &ret);
|
||||
VariantClear(&ret);
|
||||
SysFreeString(code);
|
||||
SysFreeString(lang);
|
||||
win->Release();
|
||||
return SUCCEEDED(hr);
|
||||
}
|
||||
|
||||
DWORD WINAPI WorkerThread(void*)
|
||||
{
|
||||
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
||||
|
||||
HWND lastHandled = nullptr;
|
||||
for (;;) {
|
||||
if (InterlockedCompareExchange(&g_stop, 0, 0) != 0) break;
|
||||
|
||||
HWND hwnd = FindIeServerWindow();
|
||||
if (hwnd && hwnd != lastHandled) {
|
||||
LogLine("web redirect: IE_Server window found hwnd=%p, waiting %dms", (void*)hwnd, g_cfg.delayMs);
|
||||
// 等原生导航把文档对象建好,太早拿会拿到 about:blank。
|
||||
for (int waited = 0; waited < g_cfg.delayMs && !g_stop; waited += 100) {
|
||||
Sleep(100);
|
||||
}
|
||||
|
||||
IHTMLDocument2* doc = GetDocument(hwnd);
|
||||
if (!doc) {
|
||||
LogLine("web redirect: document object unavailable hwnd=%p", (void*)hwnd);
|
||||
lastHandled = hwnd; // 失败也不无限重试同一窗口
|
||||
} else {
|
||||
const std::wstring current = DocumentUrl(doc);
|
||||
LogLine("web redirect: current url=%ls", current.c_str());
|
||||
if (IsLocalUrl(current)) {
|
||||
LogLine("web redirect: already on local page, skip");
|
||||
lastHandled = hwnd;
|
||||
} else if (NavigateDocument(doc)) {
|
||||
LogLine("web redirect: navigated to %ls", g_cfg.url.c_str());
|
||||
lastHandled = hwnd;
|
||||
} else {
|
||||
LogLine("web redirect: execScript failed hwnd=%p", (void*)hwnd);
|
||||
lastHandled = hwnd;
|
||||
}
|
||||
doc->Release();
|
||||
}
|
||||
} else if (hwnd && hwnd == lastHandled && g_cfg.force) {
|
||||
// force 模式下防止用户点原生标签页跳走。
|
||||
IHTMLDocument2* doc = GetDocument(hwnd);
|
||||
if (doc) {
|
||||
if (!IsLocalUrl(DocumentUrl(doc))) {
|
||||
if (NavigateDocument(doc)) LogLine("web redirect: pulled back to %ls", g_cfg.url.c_str());
|
||||
}
|
||||
doc->Release();
|
||||
}
|
||||
}
|
||||
|
||||
if (!hwnd) lastHandled = nullptr; // 窗口被销毁后允许下次重新接管
|
||||
for (int waited = 0; waited < 700 && !g_stop; waited += 100) {
|
||||
Sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
CoUninitialize();
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" void InstallWebRedirect()
|
||||
{
|
||||
LoadConfig();
|
||||
if (!g_cfg.enabled) {
|
||||
LogLine("web redirect: disabled by config");
|
||||
return;
|
||||
}
|
||||
if (g_cfg.url.find(L'\'') != std::wstring::npos) {
|
||||
LogLine("web redirect: url contains a quote, refused");
|
||||
return;
|
||||
}
|
||||
|
||||
InterlockedExchange(&g_stop, 0);
|
||||
HANDLE thread = CreateThread(nullptr, 0, WorkerThread, nullptr, 0, &g_threadId);
|
||||
if (!thread) {
|
||||
LogLine("web redirect: CreateThread failed err=%u", GetLastError());
|
||||
return;
|
||||
}
|
||||
CloseHandle(thread);
|
||||
LogLine("web redirect: worker started tid=%u target=%ls", g_threadId, g_cfg.url.c_str());
|
||||
}
|
||||
Reference in new issue
Block a user