feat: Add metro transit, gadgets, shop, social apps, and traffic models checkpoint

This commit is contained in:
CAtlas0784 committed 2026-10-29 01:55:01 +07:00
1 parent caa42b84ed
commit 74e2963a12
79 files changed
+527962 -496

No files matched your search

@@ -13,25 +13,517 @@ const fileName = "LuaFiles#LX6#SGUI#StoreDefine#UidLayerPanelStore.lua";
const labelEnabled = config.ui?.uidLabel?.enabled !== false;
const label = labelEnabled ? String(config.ui?.uidLabel?.text || "นี่คือเวอร์ชั่น DEV ที่ไม่ได้รับคุณภาพจากเกม Ananta GAY") : "";
const removeStock = config.ui?.removeStockConfidentialLabel !== false;
const showDiagnostics = config.ui?.showLuaDiagnostics === true;
const languageOverride = Number(config.ui?.textLanguageOverride || 0);
fs.rmSync(fastPatchRoot, { recursive: true, force: true });
fs.mkdirSync(fastPatchRoot, { recursive: true });
const qLabel = JSON.stringify(label);
const luaSource = `-- Ananta private-server fastpatch for client ${clientVersion}\n` +
// The dev-menu dictionary travels inside the fastpatch: the client's Lua can reach UnityEngine and SGUI,
// so the menu is walked and translated in Lua, with nothing injected into the process.
// The language is a config setting, not a choice of script: Run-All.ps1 rebuilds the patch on every
// start with this generator, so anything decided outside the config would be overwritten at launch.
// ui.devMenuDictionary names the Russian file; another language is the same name with its own suffix
// in the same folder. DRMK_DEV_MENU_DICT overrides both, for a one-off build.
function resolveDictionaryPath() {
if (process.env.DRMK_DEV_MENU_DICT) return path.resolve(process.env.DRMK_DEV_MENU_DICT);
const configured = String(config.ui?.devMenuDictionary || "");
if (!configured) return "";
const base = resolveProjectPath(configured);
const language = String(config.ui?.devMenuLanguage || "ru").toLowerCase();
if (language === "ru") return base;
const sibling = path.join(path.dirname(base), `translation.${language}.txt`);
if (fs.existsSync(sibling)) return sibling;
console.warn(`[UI] ui.devMenuLanguage='${language}' but ${sibling} is missing; falling back to ${base}`);
return base;
}
const dictPath = resolveDictionaryPath();
function buildLuaDictionary() {
if (!dictPath || !fs.existsSync(dictPath)) return { lua: "local RU = {}\nlocal RULES = {}\n", count: 0, rules: 0 };
const lines = fs.readFileSync(dictPath, "utf8").split(/\r?\n/);
const entries = [];
const rules = [];
for (const line of lines) {
if (!line || line.startsWith("//")) continue;
let sep = -1;
for (let i = 0; i < line.length; i++) {
if (line[i] === "\\") { i++; continue; }
if (line[i] === "=") { sep = i; break; }
}
if (sep <= 0) continue;
const key = line.slice(0, sep);
const value = line.slice(sep + 1);
if (!value) continue;
const star = key.indexOf("{*}");
if (star >= 0) {
// "已解锁{*}" -> prefix/suffix pair; the captured middle is translated on its own.
rules.push({ prefix: key.slice(0, star), suffix: key.slice(star + 3), value });
continue;
}
entries.push(` [${JSON.stringify(key)}] = ${JSON.stringify(value)},`);
}
// Longest prefix+suffix first, so a specific rule wins over a generic one.
rules.sort((a, b) => (b.prefix.length + b.suffix.length) - (a.prefix.length + a.suffix.length));
const ruleLines = rules.map(r =>
` { ${JSON.stringify(r.prefix)}, ${JSON.stringify(r.suffix)}, ${JSON.stringify(r.value)} },`);
return {
lua: `local RU = {\n${entries.join("\n")}\n}\nlocal RULES = {\n${ruleLines.join("\n")}\n}\n`,
count: entries.length,
rules: rules.length,
};
}
const dictionary = buildLuaDictionary();
const luaSource = `-- DRMK private-server fastpatch for client ${clientVersion}\n` +
`-- Persistent UID/watermark override only. Gameplay Lua is untouched.\n\n` +
`local CSPauseManager = LX6.Engine.PauseManager\n` +
`C_UidLayerPanelStore = DefClass("C_UidLayerPanelStore", C_UidLayerPanelStore, C_StoreGroup)\n` +
`GroupName2Class.UidLayerPanelStore = C_UidLayerPanelStore\n` +
`local M = C_UidLayerPanelStore\n` +
`local PRIVATE_SERVER_LABEL = ${qLabel}\n` +
`local REMOVE_STOCK_LABEL = ${removeStock ? "true" : "false"}\n\n` +
`local REMOVE_STOCK_LABEL = ${removeStock ? "true" : "false"}\n` +
`local SHOW_DIAGNOSTICS = ${showDiagnostics ? "true" : "false"}\n` +
`local LANGUAGE_OVERRIDE = ${languageOverride}\n\n` +
`-- One-shot probe: can Lua write a file? If yes the server gains a real read-back channel and the\n` +
`-- menu's own Lua functions can be inspected and wrapped, with nothing injected into the process.\n` +
`local probeDone = false\n` +
`local probeResult = "?"\n` +
`local translateCount = 0\n` +
`local hookState = "none"\n` +
`local hookedMetatables = setmetatable({}, { __mode = "k" })\n` +
`local seenText, seenCount = {}, 0\n\n` +
`-- Reports what the Lua layer can reach, straight onto the watermark: the client's current text\n` +
`-- language and whether the GM function table is bound. Every probe is wrapped in pcall so a missing\n` +
`-- binding degrades to a "?" instead of killing the panel.\n` +
`local function Probe()\n` +
` if not SHOW_DIAGNOSTICS then return "" end\n` +
` local okLang, lang = pcall(function() return LX6.Game.LanguageProfile.textLanguage end)\n` +
` local okGm = pcall(function() return L50.Gm.AutoQaFunctions ~= nil end)\n` +
` local okLoc = pcall(function() return LX6.Manager.LocalizationManager.Instance ~= nil end)\n` +
` return " | " .. probeResult .. " tr=" .. tostring(translateCount) .. " hook=" .. tostring(hookState)\n` +
` .. " gm=" .. (okGm and "ok" or "no")\n` +
` .. " loc=" .. (okLoc and "ok" or "no")\n` +
`end\n\n` +
`local function Probe1()\n` +
` if probeDone then return end\n` +
` probeDone = true\n` +
` local ok, err = pcall(function()\n` +
` local f = io.open("G:/tools/lua_probe.txt", "w")\n` +
` if not f then error("io.open returned nil") end\n` +
` f:write("lua probe ok\\n")\n` +
` local names = {}\n` +
` for k, v in pairs(_G) do\n` +
` if type(k) == "string" and (k:find("Test") or k:find("Debug") or k:find("GM") or k:find("Gm")) then\n` +
` names[#names + 1] = k .. "=" .. type(v)\n` +
` end\n` +
` end\n` +
` table.sort(names)\n` +
` f:write("globals: " .. #names .. "\\n")\n` +
` for i = 1, #names do f:write(names[i] .. "\\n") end\n` +
` f:close()\n` +
` end)\n` +
` probeResult = ok and "io=ok" or ("io=no:" .. tostring(err))\n` +
`end\n\n` +
dictionary.lua +
`\n-- Three ways to match a caption, in order: exact, numbers-as-placeholders ("设置比赛结束时间为20s"\n` +
`-- matches "设置比赛结束时间为{0}s"), and a wildcard rule ("已解锁{*}"), whose captured middle is\n` +
`-- translated on its own. Without the last two, captions carrying ids, timers or hotkeys stay Chinese.\n` +
`local function Templatize(s)\n` +
` local nums, idx = {}, 0\n` +
` local template = s:gsub("%-?%d+[%.,]?%d*", function(m)\n` +
` nums[#nums + 1] = m\n` +
` local key = "{" .. idx .. "}"\n` +
` idx = idx + 1\n` +
` return key\n` +
` end)\n` +
` return template, nums\n` +
`end\n\n` +
`local function Fill(pattern, nums)\n` +
` local out = pattern\n` +
` for i = #nums, 1, -1 do\n` +
` out = out:gsub("{" .. (i - 1) .. "}", (nums[i]:gsub("%%", "%%%%")), 1)\n` +
` end\n` +
` return out\n` +
`end\n\n` +
`local function Lookup(s)\n` +
` local exact = RU[s]\n` +
` if exact then return exact end\n` +
` local template, nums = Templatize(s)\n` +
` if #nums > 0 then\n` +
` local pattern = RU[template]\n` +
` if pattern then return Fill(pattern, nums) end\n` +
` end\n` +
` return nil\n` +
`end\n\n` +
`-- A rule's captured middle goes back through the rules rather than only through the table, so a\n` +
`-- caption wearing two markers ("[temp]" in front of a quest name that itself matches a rule) still\n` +
`-- resolves. The depth cap keeps a rule that matches its own output from looping.\n` +
`local function TranslateCore(s, depth)\n` +
` local direct = Lookup(s)\n` +
` if direct then return direct end\n` +
` if depth > 3 then return nil end\n` +
` for i = 1, #RULES do\n` +
` local prefix, suffix, value = RULES[i][1], RULES[i][2], RULES[i][3]\n` +
` local pl, sl = #prefix, #suffix\n` +
` if #s > pl + sl and s:sub(1, pl) == prefix and (sl == 0 or s:sub(#s - sl + 1) == suffix) then\n` +
` local inner = s:sub(pl + 1, #s - sl)\n` +
` local innerRu = TranslateCore(inner, depth + 1) or inner\n` +
` return (value:gsub("{%*}", (innerRu:gsub("%%", "%%%%"))))\n` +
` end\n` +
` end\n` +
` return nil\n` +
`end\n\n` +
`-- Captions reach the panel padded with stray spaces often enough to miss an exact-match table, so a\n` +
`-- second attempt strips the padding, translates the core and puts the padding back untouched.\n` +
`local function Translate(s)\n` +
` local direct = TranslateCore(s, 0)\n` +
` if direct then return direct end\n` +
` local head, core, tail = s:match("^(%s*)(.-)(%s*)$")\n` +
` if core and core ~= "" and core ~= s then\n` +
` local padded = TranslateCore(core, 0)\n` +
` if padded then return head .. padded .. tail end\n` +
` end\n` +
` return nil\n` +
`end\n\n` +
`-- Walks the dev-menu panel and swaps Chinese captions for the dictionary entry. Pure Lua: it uses the\n` +
`-- client's own UnityEngine bindings, so there is no detour and nothing to destabilise the process.\n` +
`-- Diagnostics are rewritten whenever a pass sees more labels than any pass before it: the first call\n` +
`-- happens before the menu is populated, so the earliest snapshot is the least informative one.\n` +
`local diagBest = -1\n` +
`-- The store knows its own root object; GameObject.Find by name hit a different, nearly empty\n` +
`-- "TestMainPanel" (the live panel is an instantiated clone), so the store's rootGo comes first.\n` +
`local function ResolveRoot(self)\n` +
` local ok, go = pcall(function()\n` +
` local st = self and self.bindData and self.bindData.m_Store\n` +
` local grp = st and st.group\n` +
` return grp and grp.rootGo\n` +
` end)\n` +
` if ok and go ~= nil then return go, "store" end\n` +
` return UnityEngine.GameObject.Find("TestMainPanel"), "find"\n` +
`end\n\n` +
`-- Widgets of one kind share a metatable, and that metatable owns the property assignment. Wrapping it\n` +
`-- once turns every "widget.text = ..." in the whole client into a translation point -- the same place\n` +
`-- the plugin hooked natively, except here it is plain Lua, so there is nothing to corrupt.\n` +
`-- Panels push captions into their bindData table and the binding layer moves them into the widgets, so\n` +
`-- the assignment worth catching is "bindData.something = text", not "widget.text = ...".\n` +
`local function HookTable(t, label)\n` +
` if t == nil then return label .. ":nil" end\n` +
` local mt = getmetatable(t)\n` +
` if not mt then return label .. ":no-mt" end\n` +
` if hookedMetatables[mt] then return label .. ":ok" end\n` +
` local old = rawget(mt, "__newindex")\n` +
` if type(old) ~= "function" then return label .. ":no-newindex" end\n` +
` hookedMetatables[mt] = true\n` +
` rawset(mt, "__newindex", function(obj, key, value)\n` +
` if type(value) == "string" and value ~= "" then\n` +
` local ru = Translate(value)\n` +
` if ru then\n` +
` value = ru\n` +
` translateCount = translateCount + 1\n` +
` elseif not seenText[value] then\n` +
` seenText[value] = true\n` +
` seenCount = seenCount + 1\n` +
` if seenCount <= 20000 then\n` +
` pcall(function()\n` +
` local f = io.open("G:/tools/lua_setter.txt", "a")\n` +
` if f then f:write(label .. "." .. tostring(key) .. " = " .. value .. "\\n") f:close() end\n` +
` end)\n` +
` end\n` +
` end\n` +
` end\n` +
` return old(obj, key, value)\n` +
` end)\n` +
` return label .. ":ok"\n` +
`end\n\n` +
`local function InstallTextHook(self)\n` +
` pcall(function()\n` +
` local bindState = HookTable(self and self.bindData, "bind")\n` +
` local store = self and self.bindData and self.bindData.m_Store\n` +
` local storeState = HookTable(store and store.bindData, "storebind")\n` +
` hookState = bindState .. " " .. storeState .. " " .. hookState\n` +
` end)\n` +
` if hookState:find("widget:ok") then return end\n` +
` local ok, err = pcall(function()\n` +
` local store = self and self.bindData and self.bindData.m_Store\n` +
` local widgets = store and store.m_WidgetMap\n` +
` if not widgets then hookState = "widget:no-map " .. hookState return end\n` +
` for _, w in pairs(widgets) do\n` +
` local okText = pcall(function() return w.text end)\n` +
` if okText then\n` +
` local mt = getmetatable(w)\n` +
` if not mt then hookState = "widget:no-mt " .. hookState return end\n` +
` -- The metatable answers unknown keys with an error instead of nil, so every read and\n` +
` -- write here goes through rawget/rawset and the "already hooked" flag lives on our side.\n` +
` if hookedMetatables[mt] then hookState = "widget:ok " .. hookState return end\n` +
` local oldNewIndex = rawget(mt, "__newindex")\n` +
` if type(oldNewIndex) ~= "function" then hookState = "widget:no-newindex " .. hookState return end\n` +
` hookedMetatables[mt] = true\n` +
` rawset(mt, "__newindex", function(obj, key, value)\n` +
` if key == "text" and type(value) == "string" then\n` +
` local ru = Translate(value)\n` +
` if ru then\n` +
` value = ru\n` +
` translateCount = translateCount + 1\n` +
` elseif value ~= "" and not seenText[value] then\n` +
` -- Everything that flows through here without a dictionary hit is written out:\n` +
` -- it answers whether the menu captions are set from Lua at all.\n` +
` seenText[value] = true\n` +
` seenCount = seenCount + 1\n` +
` if seenCount <= 20000 then\n` +
` pcall(function()\n` +
` local f = io.open("G:/tools/lua_setter.txt", "a")\n` +
` if f then f:write(value .. "\\n") f:close() end\n` +
` end)\n` +
` end\n` +
` end\n` +
` end\n` +
` return oldNewIndex(obj, key, value)\n` +
` end)\n` +
` hookState = "widget:ok " .. hookState\n` +
` return\n` +
` end\n` +
` end\n` +
` hookState = "widget:none " .. hookState\n` +
` end)\n` +
` if not ok then hookState = "error: " .. tostring(err) end\n` +
` pcall(function()\n` +
` local f = io.open("G:/tools/lua_hook.txt", "w")\n` +
` if f then f:write("hook=" .. tostring(hookState) .. " translated=" .. tostring(translateCount) .. "\\n") f:close() end\n` +
` end)\n` +
`end\n\n` +
`local function TranslateMenu(self)\n` +
` InstallTextHook(self)\n` +
` local ok, err = pcall(function()\n` +
` local root, how = ResolveRoot(self)\n` +
` local diag = nil\n` +
` local diagLines = {}\n` +
` diagLines[#diagLines + 1] = "root=" .. tostring(root) .. " via=" .. tostring(how)\n` +
` if root == nil then\n` +
` translateCount = -2\n` +
` diagBest = -1\n` +
` return\n` +
` end\n` +
` local n, seen, misses = 0, 0, 0\n` +
` -- The tab list and the content pages are separate panels, not children of this root: walking\n` +
` -- only the root found two labels while the menu shows dozens. So every text component in the\n` +
` -- scene is examined, and only exact dictionary hits are replaced.\n` +
` for _, tp in ipairs({SGUI.UBaseText}) do\n` +
` if tp ~= nil then\n` +
` local comps = root:GetComponentsInChildren(typeof(tp), true)\n` +
` diagLines[#diagLines + 1] = "comps=" .. tostring(comps and comps.Length or -1)\n` +
` if comps ~= nil then\n` +
` for i = 0, comps.Length - 1 do\n` +
` local c = comps[i]\n` +
` if c ~= nil then\n` +
` local t = c.text\n` +
` seen = seen + 1\n` +
` local ru = t and Translate(t)\n` +
` if ru then\n` +
` c.text = ru\n` +
` n = n + 1\n` +
` elseif t and t ~= "" then\n` +
` misses = misses + 1\n` +
` if misses <= 40 then diagLines[#diagLines + 1] = "miss: " .. tostring(t) end\n` +
` end\n` +
` end\n` +
` end\n` +
` end\n` +
` end\n` +
` end\n` +
` -- What are the menu labels actually made of? Walk the panel's own transform tree and record\n` +
` -- every component type whose name mentions text, with the object it sits on.\n` +
` if seen > diagBest then\n` +
` local queue, depth, nodes = {root.transform}, {0}, 0\n` +
` while #queue > 0 and nodes < 200 do\n` +
` local tr = table.remove(queue, 1)\n` +
` local d = table.remove(depth, 1)\n` +
` nodes = nodes + 1\n` +
` pcall(function()\n` +
` local comps2 = tr.gameObject:GetComponents(typeof(UnityEngine.Component))\n` +
` for ci = 0, (comps2 and comps2.Length or 0) - 1 do\n` +
` local cc = comps2[ci]\n` +
` if cc ~= nil then\n` +
` local tn = tostring(cc:GetType())\n` +
` if tn:find("Text") then\n` +
` local okv, vv = pcall(function() return cc.text end)\n` +
` diagLines[#diagLines + 1] = "node " .. tostring(tr.name) .. " -> " .. tn\n` +
` .. " text=" .. (okv and tostring(vv) or "?")\n` +
` end\n` +
` end\n` +
` end\n` +
` if d < 8 then\n` +
` for ki = 0, tr.childCount - 1 do\n` +
` queue[#queue + 1] = tr:GetChild(ki)\n` +
` depth[#depth + 1] = d + 1\n` +
` end\n` +
` end\n` +
` end)\n` +
` end\n` +
` diagLines[#diagLines + 1] = "nodes walked=" .. nodes\n` +
` diagBest = seen\n` +
` local f2 = io.open("G:/tools/lua_translate.txt", "w")\n` +
` if f2 then\n` +
` f2:write("seen=" .. seen .. " translated=" .. n .. " misses=" .. misses .. "\\n")\n` +
` for i = 1, #diagLines do f2:write(diagLines[i] .. "\\n") end\n` +
` f2:close()\n` +
` end\n` +
` end\n` +
` translateCount = n\n` +
` end)\n` +
` if not ok then\n` +
` translateCount = -1\n` +
` pcall(function()\n` +
` local f = io.open("G:/tools/lua_translate.txt", "w")\n` +
` if f then f:write("error: " .. tostring(err) .. "\\n") f:close() end\n` +
` end)\n` +
` end\n` +
` return ok, err\n` +
`end\n\n` +
`-- Second probe: dump the dev-menu tables. If the Chinese captions live in them, the whole menu can be\n` +
`-- translated from Lua by walking these tables -- no DLL in the process, nothing to crash.\n` +
`local function DumpValue(f, key, v, depth, maxDepth, seen)\n` +
` local pad = string.rep(" ", depth)\n` +
` local t = type(v)\n` +
` if t == "string" then\n` +
` f:write(pad .. tostring(key) .. " = \\"" .. v .. "\\"\\n")\n` +
` elseif t == "number" or t == "boolean" then\n` +
` f:write(pad .. tostring(key) .. " = " .. tostring(v) .. "\\n")\n` +
` elseif t == "function" then\n` +
` f:write(pad .. tostring(key) .. " = function\\n")\n` +
` elseif t == "table" then\n` +
` if seen[v] or depth >= maxDepth then\n` +
` f:write(pad .. tostring(key) .. " = table(...)\\n")\n` +
` return\n` +
` end\n` +
` seen[v] = true\n` +
` f:write(pad .. tostring(key) .. " = {\\n")\n` +
` local n = 0\n` +
` for k2, v2 in pairs(v) do\n` +
` n = n + 1\n` +
` if n > 400 then f:write(pad .. " ...\\n") break end\n` +
` DumpValue(f, k2, v2, depth + 1, maxDepth, seen)\n` +
` end\n` +
` f:write(pad .. "}\\n")\n` +
` else\n` +
` f:write(pad .. tostring(key) .. " = " .. t .. "\\n")\n` +
` end\n` +
`end\n\n` +
`local probe2Done = false\n` +
`local function Probe2()\n` +
` if probe2Done then return end\n` +
` probe2Done = true\n` +
` pcall(function()\n` +
` local f = io.open("G:/tools/lua_probe2.txt", "w")\n` +
` if not f then return end\n` +
` DumpValue(f, "ClientTestSuiteList", ClientTestSuiteList, 0, 4, {})\n` +
` DumpValue(f, "C_TestMainPanelStore", C_TestMainPanelStore, 0, 2, {})\n` +
` DumpValue(f, "gGmUtils", gGmUtils, 0, 1, {})\n` +
` f:close()\n` +
` end)\n` +
`end\n\n` +
`-- Third probe: the menu builds its contents when it opens, so wrap the dev-menu store's OnShow and\n` +
`-- dump what it has at that moment. Wrapping keeps the original behaviour: we call it right after.\n` +
`local probe3Armed = false\n` +
`local function Probe3()\n` +
` if probe3Armed then return end\n` +
` pcall(function()\n` +
` local S = C_TestMainPanelStore\n` +
` if not S or S.__ru_probe then return end\n` +
` S.__ru_probe = true\n` +
` probe3Armed = true\n` +
` -- The menu rebuilds its contents on tab switches too, so every method of the store re-runs the\n` +
` -- translation afterwards. Cheap: the walk only touches one panel and only while it is open.\n` +
` for key, fn in pairs(S) do\n` +
` if type(fn) == "function" and key ~= "OnShow" and key ~= "ctor" then\n` +
` local original = fn\n` +
` S[key] = function(...)\n` +
` local r = original(...)\n` +
` pcall(TranslateMenu, ...)\n` +
` return r\n` +
` end\n` +
` end\n` +
` end\n` +
` local oldShow = S.OnShow\n` +
` S.OnShow = function(self, ...)\n` +
` pcall(function()\n` +
` local f = io.open("G:/tools/lua_probe3.txt", "w")\n` +
` if not f then return end\n` +
` DumpValue(f, "ClientTestSuiteList", ClientTestSuiteList, 0, 5, {})\n` +
` DumpValue(f, "self.bindData", self and self.bindData, 0, 4, {})\n` +
` DumpValue(f, "self.data", self and self.data, 0, 3, {})\n` +
` -- Does Lua reach Unity itself? If GameObject/Find are bound, the menu can be walked the\n` +
` -- same way the plugin walked it, but without any injected code.\n` +
` for _, n in ipairs({"UnityEngine", "CS", "gCS", "typeof", "SGUI", "LX6"}) do\n` +
` f:write("global " .. n .. " = " .. type(_G[n]) .. "\\n")\n` +
` end\n` +
` pcall(function()\n` +
` local go = CS and CS.UnityEngine and CS.UnityEngine.GameObject\n` +
` f:write("CS.UnityEngine.GameObject = " .. type(go) .. "\\n")\n` +
` end)\n` +
` -- What can Lua do with a widget? If text is readable and writable here, the whole menu\n` +
` -- can be translated from Lua, with no detour anywhere in the process.\n` +
` local store = self and self.bindData and self.bindData.m_Store\n` +
` local widgets = store and store.m_WidgetMap\n` +
` if widgets then\n` +
` for name, w in pairs(widgets) do\n` +
` f:write("widget " .. tostring(name) .. " type=" .. type(w) .. "\\n")\n` +
` local mt = nil\n` +
` if mt then\n` +
` for mk, mv in pairs(mt) do\n` +
` f:write(" mt." .. tostring(mk) .. " = " .. type(mv) .. "\\n")\n` +
` if type(mv) == "table" then\n` +
` local shown = 0\n` +
` for ik, iv in pairs(mv) do\n` +
` shown = shown + 1\n` +
` if shown > 60 then f:write(" ...\\n") break end\n` +
` f:write(" " .. tostring(ik) .. " = " .. type(iv) .. "\\n")\n` +
` end\n` +
` end\n` +
` end\n` +
` end\n` +
` local okText, txt = pcall(function() return w.text end)\n` +
` f:write(" w.text -> " .. (okText and tostring(txt) or "error") .. "\\n")\n` +
` local okSet = pcall(function() w.text = tostring(txt) end)\n` +
` f:write(" w.text writable -> " .. tostring(okSet) .. "\\n")\n` +
` -- Tab captions live on the generated tab instances; find out what one looks like.\n` +
` pcall(function()\n` +
` if not w.TryGetTabInstance then return end\n` +
` for i = 0, 12 do\n` +
` local inst = w:TryGetTabInstance(i)\n` +
` if inst ~= nil then\n` +
` f:write(" tab[" .. i .. "] type=" .. type(inst) .. "\\n")\n` +
` local okT, tv = pcall(function() return inst.text end)\n` +
` f:write(" .text -> " .. (okT and tostring(tv) or "error") .. "\\n")\n` +
` if type(inst) == "table" then\n` +
` for ik, iv in pairs(inst) do\n` +
` f:write(" " .. tostring(ik) .. " = " .. type(iv) .. "\\n")\n` +
` end\n` +
` end\n` +
` end\n` +
` end\n` +
` end)\n` +
` end\n` +
` end\n` +
` f:close()\n` +
` end)\n` +
` local r\n` +
` if oldShow then r = oldShow(self, ...) end\n` +
` pcall(TranslateMenu, self)\n` +
` return r\n` +
` end\n` +
` end)\n` +
`end\n\n` +
`local function ApplyLanguageOverride()\n` +
` if LANGUAGE_OVERRIDE == 0 then return end\n` +
` pcall(function() LX6.Game.LanguageProfile.textLanguage = LANGUAGE_OVERRIDE end)\n` +
`end\n\n` +
`local function ApplyPrivateServerText(self)\n` +
` if not self or not self.bindData then return end\n` +
` local login = gPlayerManager and gPlayerManager.infoLogin and gPlayerManager.infoLogin.bindData or nil\n` +
` local pid = login and login.pid or nil\n` +
` self.bindData.uidLabel = pid ~= nil and ("UID:" .. ulong.tostring(pid)) or ""\n` +
` if REMOVE_STOCK_LABEL then self.bindData.confidentialLabel = PRIVATE_SERVER_LABEL end\n` +
` if REMOVE_STOCK_LABEL then self.bindData.confidentialLabel = PRIVATE_SERVER_LABEL .. Probe() end\n` +
` self.bindData.versionCtrl = 0\n` +
` self.bindData.showUID = 0\n` +
`end\n\n` +
@@ -41,6 +533,13 @@ const luaSource = `-- Ananta private-server fastpatch for client ${clientVersion
`M.OnAwake = function(self)\n` +
` self.RegisterMessageEvents(self, self.msgEvents)\n` +
` gLuaUIMgr.uidLayerPanelStore = self\n` +
` -- This panel exists from the login screen on and owns text widgets of its own, so the shared\n` +
` -- metatable can be wrapped here -- long before the dev menu is ever opened.\n` +
` InstallTextHook(self)\n` +
` Probe1()\n` +
` Probe2()\n` +
` Probe3()\n` +
` ApplyLanguageOverride()\n` +
` ApplyPrivateServerText(self)\n` +
`end\n\n` +
`M.OnShow = function(self, panelId, data)\n` +
@@ -61,12 +560,12 @@ const luaSource = `-- Ananta private-server fastpatch for client ${clientVersion
` local text = ""\n` +
` if clientSpeed then\n` +
` if clientSpeed ~= 0 then\n` +
` text = text .. "客户端暂停 "\n` +
` text = text .. "е®ўж€·з«Їжљ‚еЃњ "\n` +
` elseif clientSpeed >= 1 then\n` +
` text = text .. "客户端时缓:" .. gString.Format("%.2f", clientSpeed) .. " "\n` +
` text = text .. "е®ўж€·з«Їж—¶зј“пјљ" .. gString.Format("%.2f", clientSpeed) .. " "\n` +
` end\n` +
` end\n` +
` if serveSpeed then text = text .. "服务端暂停" else text = "" end\n` +
` if serveSpeed then text = text .. "жњЌеЉЎз«Їжљ‚еЃњ" else text = "" end\n` +
` self.bindData.pauseLabel = text\n` +
`end\n\n` +
`M.RefreshUID = function(self) ApplyPrivateServerText(self) end\n` +
@@ -78,6 +577,7 @@ const luaSource = `-- Ananta private-server fastpatch for client ${clientVersion
` ApplyPrivateServerText(self)\n` +
`end\n\n` +
`M.OnLanguageChange = function(self, lang) ApplyPrivateServerText(self) end\n`;
const L = lauxlib.luaL_newstate();
const luaBytes = to_luastring(luaSource);
const status = lauxlib.luaL_loadbuffer(L, luaBytes, luaBytes.length, to_luastring(fileName));