"use strict"; const fs = require("fs"); const path = require("path"); const fengari = require("fengari"); const { lua, lauxlib, to_luastring } = fengari; const { config, resolveProjectPath } = require("../private_server_config"); const clientVersion = String(config.client.version); const fastPatchRoot = path.join(resolveProjectPath(config.paths.runtimeFastpatch), clientVersion); const luaSrcRoot = resolveProjectPath("lua"); // decompiled client Lua source const fileName = "LuaFiles#LX6#SGUI#StoreDefine#UidLayerPanelStore.lua"; const labelEnabled = config.ui?.uidLabel?.enabled !== false; const label = labelEnabled ? String(config.ui?.uidLabel?.text || "Приватный Сервер Дремки!!!") : ""; const removeStock = config.ui?.removeStockConfidentialLabel !== false; const forceDebugOff = config.ui?.forceDisableDebugBools !== false; const debugClearAll = config.ui?.debugBoolsClearAll !== false; const debugClearNames = (Array.isArray(config.ui?.debugBoolsClearNames) ? config.ui.debugBoolsClearNames : []) .map((v) => String(v)) .filter((v) => v.length > 0); const tryFeatureSwitchDebugOff = config.ui?.tryFeatureSwitchDebugOff === true; const debugNamesLua = debugClearNames.length === 0 ? "{ }" : `{ ${debugClearNames.map((n) => JSON.stringify(n)).join(", ")} }`; // Debug overlay forcing. The in-game GM panel's "clear debug bool" button walks // gCS.DebugBoolMgr.GetView() -> viewList[i].list -> item.field = false + OnChange(item) // (see LX6/SGUI/StoreDefine/TestMainPanelStore.lua); we do exactly the same from a Lua // fastpatch so 坐标信息 / 水印 / other debug toggles start off instead of being switched // off by hand every session. Re-applied on panel lifecycle events and a few delays after // load, which is when the client populates the debug bool table. const luaOverlayDefs = `local FORCE_DEBUG_OFF = ${forceDebugOff ? "true" : "false"}\n` + `local DEBUG_CLEAR_ALL = ${debugClearAll ? "true" : "false"}\n` + `local DEBUG_CLEAR_NAMES = ${debugNamesLua}\n` + `local TRY_FEATURE_SWITCH_OFF = ${tryFeatureSwitchDebugOff ? "true" : "false"}\n` + `local DEBUG_REAPPLY_DELAYS = { 5, 15, 30 }\n` + `local DEBUG_DUMP_LIMIT = 80\n` + `local debugDumpLogged = false\n` + `local debugErrorReported = false\n` + `local debugScheduleStarted = false\n` + `local debugReapplyStep = 0\n` + `local featureSwitchTried = false\n` + `local watermarkReported = false\n\n` + `local function TryStep(label, fn)\n` + ` local ok, err = pcall(fn)\n` + ` if not ok and not debugErrorReported then\n` + ` debugErrorReported = true\n` + ` print("[DRMK] " .. label .. " failed: " .. tostring(err))\n` + ` end\n` + ` return ok\n` + `end\n\n` + `local function DisableUidWatermark()\n` + ` return TryStep("UIConfig watermark off", function()\n` + ` if CS and CS.SGUI and CS.SGUI.UIConfig then\n` + ` local inst = CS.SGUI.UIConfig.instance or CS.SGUI.UIConfig.Instance\n` + ` if inst then\n` + ` inst.enableImageWatermark = false\n` + ` inst.imageWatermarkText = ""\n` + ` if CS.UnityEngine and CS.UnityEngine.Color then\n` + ` inst.imageWatermarkColor = CS.UnityEngine.Color(0, 0, 0, 0)\n` + ` end\n` + ` end\n` + ` end\n` + ` end)\n` + `end\n\n` + `local function DisableMonitorWatermark()\n` + ` return TryStep("MonitorCheck watermark off", function()\n` + ` if CS and CS.LX6 and CS.LX6.Utils and CS.LX6.Utils.MonitorCheck then\n` + ` CS.LX6.Utils.MonitorCheck.IsShowWatermarkAlways = false\n` + ` CS.LX6.Utils.MonitorCheck.IsAntiMonitorAlways = false\n` + ` CS.LX6.Utils.MonitorCheck.IsAntiMonitorWithBlack = false\n` + ` end\n` + ` end)\n` + `end\n\n` + `local function DisableWatermarks()\n` + ` local uid = DisableUidWatermark()\n` + ` local monitor = DisableMonitorWatermark()\n` + ` if not watermarkReported then\n` + ` watermarkReported = true\n` + ` print("[DRMK] watermark channels: UIConfig=" .. tostring(uid) .. " MonitorCheck=" .. tostring(monitor))\n` + ` end\n` + `end\n\n` + `local function DebugBoolWanted(name, tip)\n` + ` if DEBUG_CLEAR_ALL then return true end\n` + ` if #DEBUG_CLEAR_NAMES == 0 then return false end\n` + ` local text = tostring(name or "") .. "|" .. tostring(tip or "")\n` + ` for i = 1, #DEBUG_CLEAR_NAMES do\n` + ` if string.find(text, DEBUG_CLEAR_NAMES[i], 1, true) then return true end\n` + ` end\n` + ` return false\n` + `end\n\n` + `local function TryFeatureSwitchDebugOff()\n` + ` if not TRY_FEATURE_SWITCH_OFF then return end\n` + ` local ok = pcall(function()\n` + ` if gCS.FeatureSwitch and gCS.FeatureSwitch.SetDebugItemValueFalse then\n` + ` gCS.FeatureSwitch.SetDebugItemValueFalse()\n` + ` print("[DRMK] FeatureSwitch.SetDebugItemValueFalse() called")\n` + ` end\n` + ` end)\n` + ` if not ok then print("[DRMK] FeatureSwitch.SetDebugItemValueFalse() failed (ignore)") end\n` + `end\n\n` + `local function DisableDebugBools(tag)\n` + ` if not FORCE_DEBUG_OFF then return end\n` + ` local ok, err = pcall(function()\n` + ` local viewList = gCS.DebugBoolMgr.GetView()\n` + ` local total, changed, names = 0, 0, ""\n` + ` for i = 0, viewList.Count - 1 do\n` + ` local group = viewList[i]\n` + ` local list = group and group.list\n` + ` if list then\n` + ` for j = 0, list.Count - 1 do\n` + ` local item = list[j]\n` + ` if item then\n` + ` total = total + 1\n` + ` local name, tip = item.name, item.tip\n` + ` if total <= DEBUG_DUMP_LIMIT then\n` + ` names = names .. "\\n[DRMK] " .. tostring(name) .. " | " .. tostring(tip) .. " = " .. tostring(item.field)\n` + ` end\n` + ` if item.field and DebugBoolWanted(name, tip) then\n` + ` item.field = false\n` + ` item.OnChange(item)\n` + ` changed = changed + 1\n` + ` end\n` + ` end\n` + ` end\n` + ` end\n` + ` end\n` + ` if not debugDumpLogged then\n` + ` debugDumpLogged = true\n` + ` print("[DRMK] debug bools: total=" .. tostring(total) .. " cleared=" .. tostring(changed) .. names)\n` + ` elseif changed > 0 then\n` + ` print("[DRMK] debug bools re-cleared=" .. tostring(changed) .. " (" .. tostring(tag) .. ")")\n` + ` end\n` + ` end)\n` + ` if not ok and not debugErrorReported then\n` + ` debugErrorReported = true\n` + ` print("[DRMK] debug bool clear failed: " .. tostring(err))\n` + ` end\n` + ` if not featureSwitchTried then\n` + ` featureSwitchTried = true\n` + ` TryFeatureSwitchDebugOff()\n` + ` end\n` + `end\n\n` + `local function ScheduleDebugOverlayClear()\n` + ` if not FORCE_DEBUG_OFF or debugScheduleStarted then return end\n` + ` debugScheduleStarted = true\n` + ` local function step()\n` + ` debugReapplyStep = debugReapplyStep + 1\n` + ` if debugReapplyStep > #DEBUG_REAPPLY_DELAYS then return end\n` + ` local delay = DEBUG_REAPPLY_DELAYS[debugReapplyStep]\n` + ` pcall(function()\n` + ` gLuaTimeMgrUtils.Delay(function()\n` + ` DisableDebugBools("delay" .. tostring(delay))\n` + ` step()\n` + ` end, delay, 1)\n` + ` end)\n` + ` end\n` + ` step()\n` + `end\n\n`; fs.rmSync(fastPatchRoot, { recursive: true, force: true }); fs.mkdirSync(fastPatchRoot, { recursive: true }); const qLabel = JSON.stringify(label); 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` + `local LABEL_ENABLED = ${labelEnabled ? "true" : "false"}\n\n` + luaOverlayDefs + `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` + ` if LABEL_ENABLED then\n` + ` self.bindData.uidLabel = pid ~= nil and ("UID:" .. ulong.tostring(pid)) or ""\n` + ` else\n` + ` self.bindData.uidLabel = ""\n` + ` end\n` + ` if REMOVE_STOCK_LABEL then self.bindData.confidentialLabel = PRIVATE_SERVER_LABEL end\n` + ` self.bindData.versionCtrl = 0\n` + ` self.bindData.showUID = 0\n` + ` DisableWatermarks()\n` + ` DisableDebugBools("lifecycle")\n` + `end\n\n` + `M.ctor = function(self)\n` + ` self.msgEvents = { [gEventConstants.L50_BEFORE_SWITCH_SCENE] = self.CreateAction(self, "OnBeforeSwitchScene") }\n` + `end\n\n` + `M.OnAwake = function(self)\n` + ` self.RegisterMessageEvents(self, self.msgEvents)\n` + ` gLuaUIMgr.uidLayerPanelStore = self\n` + ` ApplyPrivateServerText(self)\n` + ` ScheduleDebugOverlayClear()\n` + `end\n\n` + `M.OnShow = function(self, panelId, data)\n` + ` ApplyPrivateServerText(self)\n` + `end\n\n` + `M.OnClose = function(self) end\n\n` + `M.OnDestroy = function(self)\n` + ` self.ClearMessageEvents(self)\n` + ` gLuaUIMgr.uidLayerPanelStore = nil\n` + `end\n\n` + `M.OnActiveDeviceChange = function(self, device) end\n\n` + `M.ShowPauseInfo = function(self, serveSpeed)\n` + ` if not gGameManager.Env.isEditor or not gCS.PauseManager.showPauseTip then\n` + ` self.bindData.pauseLabel = ""\n` + ` return\n` + ` end\n` + ` local clientSpeed = CSPauseManager.Instance.PauseSpeed\n` + ` local text = ""\n` + ` if clientSpeed then\n` + ` if clientSpeed ~= 0 then\n` + ` text = text .. "客户端暂停 "\n` + ` elseif clientSpeed >= 1 then\n` + ` text = text .. "客户端时缓:" .. gString.Format("%.2f", clientSpeed) .. " "\n` + ` end\n` + ` 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` + `M.RefreshVersion = function(self) ApplyPrivateServerText(self) end\n` + `M.RefreshUIDDisplay = function(self, show)\n` + ` if self and self.bindData then self.bindData.showUID = 0 end\n` + `end\n\n` + `M.OnBeforeSwitchScene = function(self, eventId, switchSceneEventParams)\n` + ` 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)); if (status !== lua.LUA_OK) throw new Error("generated UidLayerPanelStore.lua failed Lua syntax validation"); fs.writeFileSync(path.join(fastPatchRoot, fileName), luaSource, "utf8"); // GameSwitch defaults: the private server has no GameSwitch RPC, so the client's // gGameSwitch table would stay empty and every switch-gated phone app/panel would // report "This feature is temporarily unavailable". Pre-seed all retail switches // to open (real-money charge stays closed). Server-side SyncGameSwitchToClient // still wins whenever it arrives, because M.Sync overwrites these defaults. const forceSwitches = config.ui?.forceEnableGameSwitches !== false; const switchFileName = "LuaFiles#LX6#Manager#ClientGameSwitch.lua"; // NOTE: EnableCharge is intentionally absent (real-money flow). const gameSwitchDefaults = [ "DisableImageWatermark", "NoWatermark", "EnableBuzzCenter", "EnableMall", "EnableMallBundle", "EnableMallRecommend", "EnableMallDirectSale", "EnableCheckIn", "EnableTime", "EnableDossier", "EnableRadiantChest", "EnableCloset", "EnableGachaSystem", "EnableSeasonalBattlePass", "EnableBBChat", "EnableScope", "EnableParty", "EnableSpiritTalent", "EnablePhoto", "EnableMail", "EnablePhone", "EnableFriends", "EnableAchievement", "EnableTutorial", "EnableCityPedia", "EnableNotices", "EnableCustom", "EnableInteractionAction", "EnableDutyTerminal", "EnableCatExpress", "EnableEonBug", "EnableJanitor", "EnableRadioStation", "EnableBubble", "EnableFashionStore", "Enable4SStore", "EnableProfile", "EnableClub", "EnableRanking", "EnableAkashicSystem", ]; let switchNote = "GameSwitch defaults: skipped (ui.forceEnableGameSwitches=false).\n"; const debugNote = forceDebugOff ? `Debug overlays: forcing 坐标信息/水印 class debug toggles to false (clearAll=${debugClearAll}, names=[${debugClearNames.join(", ")}]).\n` : "Debug overlays: untouched (ui.forceDisableDebugBools=false).\n"; if (forceSwitches) { const switchSource = `-- DRMK private-server fastpatch for client ${clientVersion}\n` + `-- GameSwitch defaults only. Original ClientGameSwitch.lua logic is unchanged.\n\n` + `local M = {}\n\n` + `M.Sync = function(key, value)\n` + ` M[key] = value\n\n` + ` gMessageManager:SendMessage(gEventConstants.ON_GM_GAME_SWITCH_CHANGE)\n` + `end\n\n` + gameSwitchDefaults.map((name) => `M.${name} = true\n`).join("") + `M.EnableImageWatermark = false\n` + `\n` + `gGameSwitch = M\n`; const switchBytes = to_luastring(switchSource); const switchStatus = lauxlib.luaL_loadbuffer(L, switchBytes, switchBytes.length, to_luastring(switchFileName)); if (switchStatus !== lua.LUA_OK) throw new Error("generated ClientGameSwitch.lua failed Lua syntax validation"); fs.writeFileSync(path.join(fastPatchRoot, switchFileName), switchSource, "utf8"); switchNote = `GameSwitch defaults: ${gameSwitchDefaults.length} switches pre-seeded open (EnableCharge stays closed).\n`; } fs.writeFileSync( path.join(fastPatchRoot, "changelog.txt"), `DRMK PRIVATE SERVER FASTPATCH (client ${clientVersion})\n` + `UID label: ${label}\n` + "Stock development/confidential and version-mismatch captions are suppressed.\n" + switchNote + debugNote + "No gameplay Lua overrides are included.\n", "utf8", );