GM 页:修复内嵌 IE 输入法 + 物品分类浏览
- 90CN.dll:给 IE 控件窗口链重新关联 IME 上下文,中文输入不再只能打字母 - gmweb:页面加 ime-mode:active,focus 时再主动要一次 - 物品页加分组/类型/等级筛选,不依赖中文输入也能定位物品 - 服务端指纹回绑新 DLL(1223680 / 26633404…)
This commit is contained in:
1 parent
c4ffa57bf2
commit
e9a3952a9d
5 files changed
+168
-9
No files matched your search
@@ -25,6 +25,7 @@
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
#include <imm.h>
|
||||||
#include <ole2.h>
|
#include <ole2.h>
|
||||||
#include <oleacc.h>
|
#include <oleacc.h>
|
||||||
#include <mshtml.h>
|
#include <mshtml.h>
|
||||||
@@ -159,6 +160,40 @@ HWND FindIeServerWindow()
|
|||||||
return ctx.found;
|
return ctx.found;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- 输入法
|
||||||
|
//
|
||||||
|
// 症状:页面里的输入框只能打字母,切不到中文。
|
||||||
|
// 原因:IME 上下文是窗口级的,DNF 主窗口把它握在自己手里,内嵌 IE 控件
|
||||||
|
// 这条窗口链上没有上下文,输入法候选项压根不会出现。
|
||||||
|
// 做法:给控件窗口及其子窗口重新关联线程默认 IME 上下文。这个调用是幂等
|
||||||
|
// 的,每次轮询顺手做一遍,防止游戏在切图/进出聊天时把它重置掉。
|
||||||
|
|
||||||
|
bool AttachIme(HWND hwnd)
|
||||||
|
{
|
||||||
|
if (!hwnd) return false;
|
||||||
|
if (ImmAssociateContextEx(hwnd, nullptr, IACE_DEFAULT | IACE_CHILDREN)) return true;
|
||||||
|
// 退化路径:某些环境不给默认上下文,那就自己建一个挂上去。
|
||||||
|
HIMC context = ImmCreateContext();
|
||||||
|
if (!context) return false;
|
||||||
|
if (ImmAssociateContext(hwnd, context)) return true;
|
||||||
|
ImmDestroyContext(context);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EnsureImeChain(HWND hwnd)
|
||||||
|
{
|
||||||
|
if (!hwnd) return false;
|
||||||
|
// 只给 IE_Server 挂还不够:焦点落在父窗口(Browser Example)时,
|
||||||
|
// 输入法又会被游戏主窗口抢回去,所以父链一起处理。
|
||||||
|
HWND chain[3] = { hwnd, GetParent(hwnd), nullptr };
|
||||||
|
if (chain[1]) chain[2] = GetParent(chain[1]);
|
||||||
|
bool any = false;
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
if (chain[i] && AttachIme(chain[i])) any = true;
|
||||||
|
}
|
||||||
|
return any;
|
||||||
|
}
|
||||||
|
|
||||||
// 取文档对象。WM_HTML_GETOBJECT 的返回值经 ObjectFromLresult 还原成接口指针。
|
// 取文档对象。WM_HTML_GETOBJECT 的返回值经 ObjectFromLresult 还原成接口指针。
|
||||||
IHTMLDocument2* GetDocument(HWND hwnd)
|
IHTMLDocument2* GetDocument(HWND hwnd)
|
||||||
{
|
{
|
||||||
@@ -216,10 +251,20 @@ DWORD WINAPI WorkerThread(void*)
|
|||||||
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
||||||
|
|
||||||
HWND lastHandled = nullptr;
|
HWND lastHandled = nullptr;
|
||||||
|
HWND imeHwnd = nullptr;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (InterlockedCompareExchange(&g_stop, 0, 0) != 0) break;
|
if (InterlockedCompareExchange(&g_stop, 0, 0) != 0) break;
|
||||||
|
|
||||||
HWND hwnd = FindIeServerWindow();
|
HWND hwnd = FindIeServerWindow();
|
||||||
|
if (hwnd) {
|
||||||
|
// 每轮都重挂一次输入法:游戏在切图、进出聊天时会把上下文收回去。
|
||||||
|
// 日志只在窗口变化时写,避免把 trace 刷满。
|
||||||
|
const bool imeOk = EnsureImeChain(hwnd);
|
||||||
|
if (hwnd != imeHwnd) {
|
||||||
|
LogLine("web ime: attach %s hwnd=%p", imeOk ? "ok" : "failed", (void*)hwnd);
|
||||||
|
imeHwnd = hwnd;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (hwnd && hwnd != lastHandled) {
|
if (hwnd && hwnd != lastHandled) {
|
||||||
LogLine("web redirect: IE_Server window found hwnd=%p, waiting %dms", (void*)hwnd, g_cfg.delayMs);
|
LogLine("web redirect: IE_Server window found hwnd=%p, waiting %dms", (void*)hwnd, g_cfg.delayMs);
|
||||||
// 等原生导航把文档对象建好,太早拿会拿到 about:blank。
|
// 等原生导航把文档对象建好,太早拿会拿到 about:blank。
|
||||||
@@ -257,7 +302,10 @@ DWORD WINAPI WorkerThread(void*)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hwnd) lastHandled = nullptr; // 窗口被销毁后允许下次重新接管
|
if (!hwnd) { // 窗口被销毁后允许下次重新接管
|
||||||
|
lastHandled = nullptr;
|
||||||
|
imeHwnd = nullptr;
|
||||||
|
}
|
||||||
for (int waited = 0; waited < 700 && !g_stop; waited += 100) {
|
for (int waited = 0; waited < 700 && !g_stop; waited += 100) {
|
||||||
Sleep(100);
|
Sleep(100);
|
||||||
}
|
}
|
||||||
|
|||||||
+88
-3
@@ -257,15 +257,71 @@
|
|||||||
log('已选物品:' + it.name + '(id ' + it.item_id + ')', 'tip');
|
log('已选物品:' + it.name + '(id ' + it.item_id + ')', 'tip');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 分组 -> 类型选项。装备/时装/宠物走 tag(PVF 的 equipment type),
|
||||||
|
道具走 segment(服务端按 stackable type 归类成中文段)。
|
||||||
|
有了这两级下拉,不敲键盘也能把范围缩到十几页。 */
|
||||||
|
var TYPE_OPTIONS = {
|
||||||
|
equipment: [
|
||||||
|
['tag:weapon', '武器'], ['tag:coat', '上衣'], ['tag:shoulder', '头肩'],
|
||||||
|
['tag:pants', '下装'], ['tag:shoes', '鞋'], ['tag:waist', '腰带'],
|
||||||
|
['tag:amulet', '项链'], ['tag:wrist', '手镯'], ['tag:ring', '戒指'],
|
||||||
|
['tag:support', '辅助装备'], ['tag:magic stone', '魔法石'],
|
||||||
|
['tag:earring', '耳环'], ['tag:support weapon', '辅助武器'],
|
||||||
|
['tag:title name', '称号'], ['tag:name tag', '名牌']
|
||||||
|
],
|
||||||
|
avatar: [
|
||||||
|
['tag:hat avatar', '帽子'], ['tag:hair avatar', '头发'], ['tag:face avatar', '脸部'],
|
||||||
|
['tag:coat avatar', '上衣'], ['tag:breast avatar', '胸部'], ['tag:waist avatar', '腰部'],
|
||||||
|
['tag:pants avatar', '下装'], ['tag:shoes avatar', '鞋'], ['tag:skin avatar', '皮肤'],
|
||||||
|
['tag:aurora avatar', '光环'], ['tag:weapon avatar', '武器装扮']
|
||||||
|
],
|
||||||
|
pet: [
|
||||||
|
['tag:creature', '宠物'], ['tag:artifact red', '神器·红'],
|
||||||
|
['tag:artifact blue', '神器·蓝'], ['tag:artifact green', '神器·绿']
|
||||||
|
],
|
||||||
|
stackable: [
|
||||||
|
['seg:消耗品', '消耗品'], ['seg:材料', '材料'], ['seg:特殊材料', '特殊材料'],
|
||||||
|
['seg:任务品', '任务品'], ['seg:徽章', '徽章'], ['seg:副职业材料', '副职业材料']
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
function fillTypeOptions() {
|
||||||
|
var opts = TYPE_OPTIONS[$('in-group').value] || [];
|
||||||
|
var h = ['<option value="">不限</option>'];
|
||||||
|
for (var i = 0; i < opts.length; i++) {
|
||||||
|
h.push('<option value="' + esc(opts[i][0]) + '">' + esc(opts[i][1]) + '</option>');
|
||||||
|
}
|
||||||
|
$('in-tag').innerHTML = h.join('');
|
||||||
|
}
|
||||||
|
|
||||||
function loadItems(page) {
|
function loadItems(page) {
|
||||||
var q = $('q-item').value;
|
var q = $('q-item').value;
|
||||||
var size = parseInt($('in-page-size').value, 10) || 20;
|
var size = parseInt($('in-page-size').value, 10) || 20;
|
||||||
var p = page || 1;
|
var p = page || 1;
|
||||||
if (p < 1) { p = 1; }
|
if (p < 1) { p = 1; }
|
||||||
S.pageSize = size;
|
S.pageSize = size;
|
||||||
var offset = (p - 1) * size;
|
|
||||||
api('/dnf/gm/items/page?limit=' + size + '&offset=' + offset +
|
var group = $('in-group').value;
|
||||||
'&q=' + encodeURIComponent(q || ''), 'GET', null, function (d) {
|
var picked = $('in-tag').value;
|
||||||
|
var minLv = parseInt($('in-lv-min').value, 10);
|
||||||
|
var maxLv = parseInt($('in-lv-max').value, 10);
|
||||||
|
if (isNaN(minLv)) { minLv = 0; }
|
||||||
|
if (isNaN(maxLv)) { maxLv = 0; }
|
||||||
|
|
||||||
|
var qs = 'limit=' + size + '&offset=' + ((p - 1) * size) +
|
||||||
|
'&q=' + encodeURIComponent(q || '');
|
||||||
|
if (group) { qs += '&group=' + encodeURIComponent(group); }
|
||||||
|
if (picked) {
|
||||||
|
if (picked.indexOf('tag:') === 0) {
|
||||||
|
qs += '&tag=' + encodeURIComponent(picked.substring(4));
|
||||||
|
} else if (picked.indexOf('seg:') === 0) {
|
||||||
|
qs += '&segment=' + encodeURIComponent(picked.substring(4));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (minLv > 0) { qs += '&min_level=' + minLv; }
|
||||||
|
if (maxLv > 0) { qs += '&max_level=' + maxLv; }
|
||||||
|
|
||||||
|
api('/dnf/gm/items/page?' + qs, 'GET', null, function (d) {
|
||||||
if (!d) { return; }
|
if (!d) { return; }
|
||||||
var pageData = d.data || {};
|
var pageData = d.data || {};
|
||||||
S.items = pageData.items || [];
|
S.items = pageData.items || [];
|
||||||
@@ -368,6 +424,17 @@
|
|||||||
if (el) { el.onclick = fn; }
|
if (el) { el.onclick = fn; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 内嵌 IE 控件的输入法常被游戏主窗口占着,focus 时再主动要一次。
|
||||||
|
ime-mode:active 是 IE 私有属性,别的浏览器会忽略,不影响兼容。 */
|
||||||
|
function forceIME() {
|
||||||
|
var els = document.getElementsByTagName('input');
|
||||||
|
for (var i = 0; i < els.length; i++) {
|
||||||
|
els[i].onfocus = function () {
|
||||||
|
try { this.style.imeMode = 'active'; } catch (e) { }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
bindTabs();
|
bindTabs();
|
||||||
|
|
||||||
@@ -389,11 +456,29 @@
|
|||||||
bind('btn-mail', sendMail);
|
bind('btn-mail', sendMail);
|
||||||
|
|
||||||
$('in-page-size').onchange = function () { loadItems(1); };
|
$('in-page-size').onchange = function () { loadItems(1); };
|
||||||
|
$('in-group').onchange = function () { fillTypeOptions(); loadItems(1); };
|
||||||
|
$('in-tag').onchange = function () { loadItems(1); };
|
||||||
|
|
||||||
|
/* 等级快捷档:点一下就把区间填好并重新查,省得手敲。 */
|
||||||
|
var lvBtns = document.getElementsByTagName('button');
|
||||||
|
for (var b = 0; b < lvBtns.length; b++) {
|
||||||
|
var spec = lvBtns[b].getAttribute('data-lv');
|
||||||
|
if (!spec) { continue; }
|
||||||
|
lvBtns[b].onclick = function () {
|
||||||
|
var pair = this.getAttribute('data-lv').split('-');
|
||||||
|
$('in-lv-min').value = pair[0];
|
||||||
|
$('in-lv-max').value = pair[1];
|
||||||
|
loadItems(1);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
$('q-char').onkeydown = function (e) { if ((e || window.event).keyCode === 13) { loadChars(); } };
|
$('q-char').onkeydown = function (e) { if ((e || window.event).keyCode === 13) { loadChars(); } };
|
||||||
$('q-item').onkeydown = function (e) { if ((e || window.event).keyCode === 13) { loadItems(1); } };
|
$('q-item').onkeydown = function (e) { if ((e || window.event).keyCode === 13) { loadItems(1); } };
|
||||||
|
|
||||||
|
fillTypeOptions();
|
||||||
|
|
||||||
renderPicked();
|
renderPicked();
|
||||||
|
forceIME();
|
||||||
log('GM 控制台已加载', 'tip');
|
log('GM 控制台已加载', 'tip');
|
||||||
loadStatus();
|
loadStatus();
|
||||||
loadChars();
|
loadChars();
|
||||||
|
|||||||
+24
-1
@@ -56,7 +56,30 @@
|
|||||||
|
|
||||||
<div class="pane" id="pane-item">
|
<div class="pane" id="pane-item">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<input id="q-item" class="inp" placeholder="物品名关键字,如 太刀 / 强化">
|
<label class="lb">分组</label>
|
||||||
|
<select id="in-group" class="inp sm">
|
||||||
|
<option value="">全部</option>
|
||||||
|
<option value="equipment">装备</option>
|
||||||
|
<option value="avatar">时装</option>
|
||||||
|
<option value="pet">宠物 / 神器</option>
|
||||||
|
<option value="stackable">道具</option>
|
||||||
|
<option value="other">其他</option>
|
||||||
|
</select>
|
||||||
|
<label class="lb">类型</label>
|
||||||
|
<select id="in-tag" class="inp sm"><option value="">不限</option></select>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<label class="lb">等级</label>
|
||||||
|
<input id="in-lv-min" class="inp xs" value="">
|
||||||
|
<span class="dim">~</span>
|
||||||
|
<input id="in-lv-max" class="inp xs" value="">
|
||||||
|
<button class="btn sm" data-lv="1-20">1-20</button>
|
||||||
|
<button class="btn sm" data-lv="21-40">21-40</button>
|
||||||
|
<button class="btn sm" data-lv="41-60">41-60</button>
|
||||||
|
<button class="btn sm" data-lv="61-90">61-90</button>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<input id="q-item" class="inp" placeholder="物品名关键字(可留空,用上面分类浏览)">
|
||||||
<select id="in-page-size" class="inp sm">
|
<select id="in-page-size" class="inp sm">
|
||||||
<option value="10">每页 10 行</option>
|
<option value="10">每页 10 行</option>
|
||||||
<option value="20" selected>每页 20 行</option>
|
<option value="20" selected>每页 20 行</option>
|
||||||
|
|||||||
+5
-2
@@ -48,8 +48,11 @@ table.kv td { padding: 4px 0; border-bottom: 1px solid #232734; color: #dfe3ea;
|
|||||||
background: #0f1116; border: 1px solid #2f3547; color: #dfe3ea;
|
background: #0f1116; border: 1px solid #2f3547; color: #dfe3ea;
|
||||||
padding: 4px 6px; font: 12px "Microsoft YaHei", Tahoma, sans-serif;
|
padding: 4px 6px; font: 12px "Microsoft YaHei", Tahoma, sans-serif;
|
||||||
width: 240px; outline: none;
|
width: 240px; outline: none;
|
||||||
|
/* 内嵌 IE 控件里输入法默认是关的,这里强制激活,否则只能打英文 */
|
||||||
|
ime-mode: active;
|
||||||
}
|
}
|
||||||
.inp:focus { border-color: #e8c979; }
|
.inp:focus { border-color: #e8c979; ime-mode: active; }
|
||||||
|
input[type="text"], textarea { ime-mode: active; }
|
||||||
.inp.sm { width: 110px; }
|
.inp.sm { width: 110px; }
|
||||||
.inp.xs { width: 60px; }
|
.inp.xs { width: 60px; }
|
||||||
.inp.xs2 { width: 84px; }
|
.inp.xs2 { width: 84px; }
|
||||||
@@ -83,7 +86,7 @@ select.inp { padding: 3px 2px; }
|
|||||||
.empty { padding: 12px; color: #666e80; text-align: center; }
|
.empty { padding: 12px; color: #666e80; text-align: center; }
|
||||||
.dim { color: #7c8496; }
|
.dim { color: #7c8496; }
|
||||||
.pager { margin: 6px 0 2px; }
|
.pager { margin: 6px 0 2px; }
|
||||||
.pager .btn.sm { padding: 3px 10px; }
|
.btn.sm { padding: 3px 10px; font-size: 11px; }
|
||||||
.pg { color: #8d94a6; font-size: 11px; margin: 0 8px; }
|
.pg { color: #8d94a6; font-size: 11px; margin: 0 8px; }
|
||||||
|
|
||||||
#log {
|
#log {
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ const (
|
|||||||
boundMovieSHA256 = "D62E3623C39CD9FCED5F6C4C7F5E6E18BEB69EB269ABF5D713EDE08EE8C926A1"
|
boundMovieSHA256 = "D62E3623C39CD9FCED5F6C4C7F5E6E18BEB69EB269ABF5D713EDE08EE8C926A1"
|
||||||
boundMovieBytes = int64(22846532)
|
boundMovieBytes = int64(22846532)
|
||||||
|
|
||||||
boundDllSHA256 = "E947A0C2E36EA24C3F2F557E7D3207C76F9B6E63192E2555B6D4B73593EE17F1"
|
boundDllSHA256 = "266334040E714A429F36AB4CB69BB6824F2D7DD39AAB97D07AFBCF73545E5640"
|
||||||
boundDllBytes = int64(1223168)
|
boundDllBytes = int64(1223680)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user