using System.IO; using System.Globalization; using System.Reflection; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Threading; using PCL.Core.App; using PCL.Core.Logging; using PCL.Core.UI; using PCL.Network; using PCL.Core.App.Localization; namespace PCL; public partial class PageLaunchRight : IRefreshable { public PageLaunchRight() { InitializeComponent(); onlineLoader = new ModLoader.LoaderTask( Lang.Text("Launch.Homepage.Task.Download"), OnlineLoaderSub) { reloadTimeout = 10 * 60 * 1000 }; Loaded += (_, _) => Init(); Loaded += (_, _) => Refresh(); Unloaded += (_, _) => _DisposeHomepageLiveWatcher(); } private void Init() { PanBack.ScrollToHome(); PanScroll = PanBack; // 不知道为啥不能在 XAML 设置 PanLog.Visibility = ModBase.modeDebug ? Visibility.Visible : Visibility.Collapsed; // 社区版提示 PanHint.Visibility = States.Hint.CEMessage ? Visibility.Visible : Visibility.Collapsed; LabHint1.Text = Lang.Text("Launch.Right.CommunityHint.Message"); LabHint2.Text = Lang.Text("Launch.Right.CommunityHint.HidePrompt"); _EnsureHomepageLiveWatcher(); } // 暂时关闭快照版提示 private void BtnHintClose_Click(object sender, EventArgs e) { var input = ModMain.MyMsgBoxInput(Lang.Text("Launch.Right.CommunityHint.InputTitle")); if (string.IsNullOrWhiteSpace(input)) return; input = new string(input.Where(char.IsAsciiLetter).ToArray()).ToLower(); if (input.Contains("pclcommunity")) { ModAnimation.AniDispose(PanHint, true); States.Hint.CEMessage = false; } else { HintService.Hint(Lang.Text("Launch.Right.CommunityHint.WrongInput")); } } #region 主页 /// /// 刷新主页。 /// private void Refresh() { ModBase.RunInNewThread(() => { try { lock (refreshLock) { RefreshReal(); } } catch (Exception ex) { ModBase.Log( ex, "加载 PCL 主页自定义信息失败", ModBase.modeDebug ? ModBase.LogLevel.Msgbox : ModBase.LogLevel.Hint, userSummary: Lang.Text("Launch.Error.OperationFailed")); } }, $"刷新主页 #{ModBase.GetUuid()}"); } private void RefreshReal() { var content = ""; string url = null; var uiCustomType = (int)Config.Preference.Homepage.Type; if (uiCustomType == 1) { // 本地文件 LogWrapper.Info("[Page] 主页自定义数据来源:本地文件"); content = ModBase.ReadFile(Path.Combine(ModBase.exePath, "PCL", "Custom.xaml")); } else if (uiCustomType == 2) { // 网络文件 url = (string)Config.Preference.Homepage.CustomUrl; content = LoadFromNetwork(url); } else if (uiCustomType == 3) { // 预设主页 var preset = (int)Config.Preference.Homepage.SelectedPreset; switch (preset) { case 0: LogWrapper.Info("[Page] 主页预设:你知道吗"); var hintText = GetRandomHint(); content = $@" "; break; case 1: LogWrapper.Info("[Page] 主页预设:回声洞 已被移除"); ModMain.MyMsgBox(Lang.Text("Launch.Homepage.Preset.EchoCave.Removed")); return; case 2: LogWrapper.Info("[Page] 主页预设:Minecraft 新闻"); url = "https://news.bugjump.net"; content = LoadFromNetwork(url); break; // case 3: // LogWrapper.Info("[Page] 主页预设:简单主页"); // url = "https://pclhomeplazaoss.lingyunawa.top:26994/d/Homepages/MFn233/Custom.xaml"; // content = LoadFromNetwork(url); // break; case 3: LogWrapper.Info("[Page] 主页预设:每日整合包推荐"); url = "https://pclsub.sodamc.com/"; content = LoadFromNetwork(url); break; case 4: LogWrapper.Info("[Page] 主页预设:Minecraft 皮肤推荐"); url = "https://forgepixel.com/pcl_sub_file"; content = LoadFromNetwork(url); break; case 5: LogWrapper.Info("[Page] 主页预设:OpenBMCLAPI 仪表盘 Lite"); url = "https://pcl-bmcl.milu.ink/"; content = LoadFromNetwork(url); break; // case 7: // LogWrapper.Info("[Page] 主页预设:主页市场"); // url = "https://pclhomeplazaoss.lingyunawa.top:26994/d/Homepages/JingHai-Lingyun/Custom.xaml"; // content = LoadFromNetwork(url); // break; // case 8: // LogWrapper.Info("[Page] 主页预设:更新日志"); // url = "https://pclhomeplazaoss.lingyunawa.top:26994/d/Homepages/Joker2184/UpdateHomepage.xaml"; // content = LoadFromNetwork(url); // break; case 6: LogWrapper.Info("[Page] 主页预设:PCL 新功能说明书"); url = "https://raw.gitcode.com/WForst-Breeze/WhatsNewPCL/raw/main/Custom.xaml"; content = LoadFromNetwork(url); break; // case 10: // LogWrapper.Info("[Page] 主页预设:OpenMCIM Dashboard"); // url = "https://files.mcimirror.top/PCL"; // content = LoadFromNetwork(url); // break; case 7: LogWrapper.Info("[Page] 主页预设:杂志主页"); url = "https://pclhomeplazaoss.lingyunawa.top:26994/d/Homepages/Ext1nguisher/Custom.xaml"; content = LoadFromNetwork(url); break; case 8: LogWrapper.Info("[Page] 主页预设:PCL GitHub 仪表盘"); url = "https://ddf.pcl-community.org/Custom.xaml"; content = LoadFromNetwork(url); break; case 9: LogWrapper.Info("[Page] 主页预设:Minecraft 更新摘要"); url = "https://raw.gitcode.com/ENC_Euphony/PCL-AI-Summary-HomePage/raw/master/Custom.xaml"; content = LoadFromNetwork(url); break; case 10: LogWrapper.Info("[Page] 主页预设:今日新闻热点"); url = "https://pcl.wyc-w.top/index.xaml"; content = LoadFromNetwork(url); break; case 11: LogWrapper.Info("[Page] 主页预设:Minecraft 芝士站"); url = "https://www.xxag.top/mkss"; content = LoadFromNetwork(url); break; case 12: LogWrapper.Info("[Page] 主页预设:整合包推荐引擎"); url = "https://qawsedrftgyhujiko.fun/pcl2/Custom.xaml"; content = LoadFromNetwork(url); break; case 13: LogWrapper.Info("[Page] 主页预设:Bangumi 番剧主页"); url = "https://bangumi.p.kaphia.qzz.io"; content = LoadFromNetwork(url); break; case 14: LogWrapper.Info("[Page] 主页预设:PCL CE 公告栏"); url = "https://s3.pysio.online/pcl2-ce/apiv2/pages/announce.xaml"; content = LoadFromNetwork(url); break; case 15: LogWrapper.Info("[Page] 主页预设:Minecraft 信息流"); Dispatcher.Invoke(() => { if (ModMain.frmHomepageNews is null) ModMain.frmHomepageNews = new PageHomepageNewsView(); PanCustom.Children.Clear(); PanCustom.Children.Add(ModMain.frmHomepageNews); }); return; } } ModBase.RunInUi(() => LoadContent(content)); } /// /// 根据 URL 加载网络内容,优先使用缓存 /// private string LoadFromNetwork(string url) { if (string.IsNullOrWhiteSpace(url)) return ""; var cachePath = Path.Combine(ModBase.pathTemp, "Cache", "Custom.xaml"); var cachedUrl = (string)States.UI.SavedHomepageUrl; if (url == cachedUrl && File.Exists(cachePath)) { LogWrapper.Info("[Page] 主页自定义数据来源:联网缓存文件"); // 后台更新缓存 onlineLoader.Start(url); return ModBase.ReadFile(cachePath); } LogWrapper.Info("[Page] 主页自定义数据来源:联网全新下载"); HintWrapper.Show(Lang.Text("Launch.Homepage.Loading")); ModBase.RunInUiWait(() => LoadContent("")); // 先清空页面 States.UI.SavedHomepageVersion = ""; onlineLoader.Start(url); // 下载完成后将会再次触发更新 return ""; } private readonly object refreshLock = new(); public static string GetRandomHint(bool enableLengthLimit = false, bool raw = false) { string[]? lines = null; // 外部文件 var externalPath = Path.Combine(ModBase.exePath, "PCL", "hints.txt"); if (File.Exists(externalPath)) { try { lines = File.ReadAllLines(externalPath) .Where(l => !string.IsNullOrWhiteSpace(l)) .Select(l => l.Trim()) .ToArray(); } catch { ModBase.Log( Lang.Text("Launch.Homepage.Error.ExternalFile", externalPath), ModBase.LogLevel.Hint, userSummary: Lang.Text("Launch.Homepage.Error.ExternalFile", externalPath)); } } // 嵌入式资源 if (lines is null || lines.Length == 0) { var langCode = LocalizationService.CurrentLanguage.Code; lines = _LoadEmbeddedHints(langCode) ?? _LoadEmbeddedHints(LocalizationService.DefaultLanguageCode); } // 长度限制 if (enableLengthLimit) { var shortLines = lines.Where(l => l.Length < 50).ToArray(); if (shortLines.Length > 0) lines = shortLines; } // 随机返回 var hint = lines[Random.Shared.Next(lines.Length)]; return raw ? hint : hint.Replace("&", "&").Replace("<", "<").Replace(">", ">").Replace("\"", """); } private static string[]? _LoadEmbeddedHints(string langCode) { try { var uri = new Uri($"pack://application:,,,/Plain Craft Launcher 2;component/Resources/hints/{langCode}.txt", UriKind.Absolute); using var stream = Application.GetResourceStream(uri)?.Stream; if (stream is null) return null; using var reader = new StreamReader(stream); return reader.ReadToEnd() .Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries) .Where(l => !string.IsNullOrWhiteSpace(l)) .Select(l => l.Trim()) .ToArray(); } catch { return null; } } // 联网获取主页文件 private readonly ModLoader.LoaderTask onlineLoader; private void OnlineLoaderSub(ModLoader.LoaderTask task) { var address = task.input; // #3721 中连续触发两次导致内容变化 try { // 获取版本校验地址 string versionAddress; if (address.Contains(".xaml")) { versionAddress = address.Replace(".xaml", ".xaml.ini"); } else { versionAddress = address.BeforeFirst("?"); if (!versionAddress.EndsWith("/")) versionAddress += "/"; versionAddress += "version"; if (address.Contains("?")) versionAddress += "?" + address.AfterFirst("?"); } // 校验版本 var version = ""; var needDownload = true; try { version = Requester.FetchString(versionAddress); if (version.Length > 1000) throw new Exception($"获取的主页版本过长({version.Length} 字符)"); var currentVersion = States.UI.SavedHomepageVersion; if (!string.IsNullOrEmpty(version) && !string.IsNullOrEmpty(currentVersion) && (version ?? "") == (currentVersion ?? "")) { ModBase.Log($"[Page] 当前缓存的主页已为最新,当前版本:{version},检查源:{versionAddress}"); needDownload = false; } else { ModBase.Log($"[Page] 需要下载联网主页,当前版本:{version},检查源:{versionAddress}"); } } catch (Exception exx) { ModBase.Log(exx, "联网获取主页版本失败", ModBase.LogLevel.Developer); ModBase.Log($"[Page] 无法检查联网主页版本,将直接下载,检查源:{versionAddress}"); } // 实际下载 if (needDownload) { var fileContent = Requester.FetchString(address); ModBase.Log($"[Page] 已联网下载主页,内容长度:{fileContent.Length},来源:{address}"); States.UI.SavedHomepageUrl = address; States.UI.SavedHomepageVersion = version; ModBase.WriteFile(ModBase.pathTemp + @"Cache\Custom.xaml", fileContent); } // 要求刷新 ModBase.RunInUi(Refresh); // 不直接调用 Refresh,以防止死循环(#6245) } catch (Exception ex) { ModBase.Log( ex, Lang.Text("Launch.Homepage.Error.Download", address), ModBase.modeDebug ? ModBase.LogLevel.Msgbox : ModBase.LogLevel.Hint, userSummary: Lang.Text("Launch.Homepage.Error.Download", address)); } } /// /// 立即强制刷新主页。 /// 必须在 UI 线程调用。 /// public void ForceRefresh() { ModBase.Log("[Page] 要求强制刷新主页"); ClearCache(); // 实际的刷新 if (ModMain.frmMain.pageCurrent.page == FormMain.PageType.Launch) { PanBack.ScrollToHome(); Refresh(); } else { ModMain.frmMain.PageChange(FormMain.PageType.Launch); } } void IRefreshable.Refresh() { ForceRefresh(); } /// /// 清空主页缓存信息。 /// private void ClearCache() { loadedContentHash = -1; onlineLoader.input = ""; States.UI.SavedHomepageUrl = ""; States.UI.SavedHomepageVersion = ""; ModBase.Log("[Page] 已清空主页缓存"); } /// /// 从文本内容中加载主页。 /// 必须在 UI 线程调用。 /// private void LoadContent(string content) { lock (loadContentLock) { // 如果加载目标内容一致则不加载 var hash = content.GetHashCode(); if (hash == loadedContentHash) { _ApplyHomepageLivePatchesFromFile(); return; } loadedContentHash = hash; // 实际加载内容 PanCustom.Children.Clear(); if (string.IsNullOrWhiteSpace(content)) { ModBase.Log("[Page] 实例化:清空主页 UI,来源为空"); return; } var loadStartTime = DateTime.Now; try { content = ModMain.ArgumentReplace(content); while (content.Contains("xmlns")) content = content.RegexReplace("xmlns[^\"']*(\"|')[^\"']*(\"|')", "").Replace("xmlns", ""); content = $"{content}"; ModBase.Log($"[Page] 实例化:加载主页 UI 开始,最终内容长度:{content.Count()}"); PanCustom.Children.Add((UIElement)ModBase.GetObjectFromXML(content, out var sanitizeResult)); _ShowSanitizeHints(sanitizeResult); _ApplyHomepageLivePatchesFromFile(); } catch (Exception ex) { if (ModBase.modeDebug) { ModBase.Log(ex, $"加载失败的主页内容:\r\n{content}"); if (ModMain.MyMsgBox( ex is UnauthorizedAccessException ? ex.Message : Lang.Text("Launch.Homepage.LoadFailed.Message", ex), Lang.Text("Launch.Homepage.LoadFailed.Title"), Lang.Text("Launch.Homepage.LoadFailed.Retry"), Lang.Text("Common.Action.Cancel")) == 1) goto Refresh; // 防止 SyncLock 死锁 } else { ModBase.Log( ex, Lang.Text("Launch.Homepage.LoadFailed.Title"), ModBase.LogLevel.Hint, userSummary: Lang.Text("Launch.Homepage.LoadFailed.Title")); } return; } var loadCostTime = (DateTime.Now - loadStartTime).Milliseconds; ModBase.Log($"[Page] 实例化:加载主页 UI 完成,耗时 {loadCostTime}ms"); if (loadCostTime > 3000) HintService.Hint(Lang.Text("Launch.Homepage.SlowWarning", Lang.Number(Math.Round(loadCostTime / 1000d, 1), "N1"))); } return; Refresh: ; ForceRefresh(); } private int loadedContentHash = -1; private readonly object loadContentLock = new(); private static void _ShowSanitizeHints(XamlEventSanitizer.SanitizeResult result) { foreach (var unsupported in result.UnsupportedTypesFound) HintService.Hint(Lang.Text("Event.Sanitize.UnsupportedTypeHint", unsupported), HintType.Error); foreach (var unknown in result.UnrecognizedTypes) HintService.Hint(Lang.Text("Event.Sanitize.UnknownTypeHint", unknown), HintType.Error); } private const string homepageLivePatchFileName = "CustomLive.json"; private const string homepageLiveSupportFileName = "CustomLive.supported.json"; // Keep the reflection patch surface explicit because patch files are written by external tools. private static readonly Dictionary _homepageLiveAllowedProperties = new(StringComparer.OrdinalIgnoreCase) { ["text"] = "Text", ["title"] = "Title", ["info"] = "Info", ["tooltip"] = "ToolTip", ["visibility"] = "Visibility", ["isEnabled"] = "IsEnabled", ["opacity"] = "Opacity" }; private FileSystemWatcher? _homepageLiveWatcher; private DispatcherTimer? _homepageLivePatchTimer; private void _EnsureHomepageLiveWatcher() { if (_homepageLiveWatcher != null) return; if ((int)Config.Preference.Homepage.Type != 1) return; try { var directory = _GetHomepageLiveDirectory(); Directory.CreateDirectory(directory); _WriteHomepageLiveSupportMarker(directory); _homepageLiveWatcher = new FileSystemWatcher(directory, homepageLivePatchFileName) { NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size | NotifyFilters.FileName }; _homepageLiveWatcher.Changed += (_, _) => _QueueHomepageLivePatchApply(); _homepageLiveWatcher.Created += (_, _) => _QueueHomepageLivePatchApply(); _homepageLiveWatcher.Renamed += (_, _) => _QueueHomepageLivePatchApply(); _homepageLiveWatcher.EnableRaisingEvents = true; _QueueHomepageLivePatchApply(); } catch (Exception ex) { ModBase.Log(ex, "[Page] Failed to start custom homepage live patch watcher", ModBase.LogLevel.Developer); } } private void _DisposeHomepageLiveWatcher() { try { _homepageLiveWatcher?.Dispose(); } catch (Exception ex) { ModBase.Log(ex, "[Page] Failed to dispose custom homepage live patch watcher", ModBase.LogLevel.Developer); } _homepageLiveWatcher = null; try { if (_homepageLivePatchTimer != null) { _homepageLivePatchTimer.Stop(); _homepageLivePatchTimer.Tick -= _HomepageLivePatchTimerTick; _homepageLivePatchTimer = null; } } catch (Exception ex) { ModBase.Log(ex, "[Page] Failed to dispose custom homepage live patch debounce timer", ModBase.LogLevel.Developer); } _DeleteHomepageLiveSupportMarker(); } private void _QueueHomepageLivePatchApply() { ModBase.RunInUi(() => { _homepageLivePatchTimer ??= new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(120) }; _homepageLivePatchTimer.Tick -= _HomepageLivePatchTimerTick; _homepageLivePatchTimer.Tick += _HomepageLivePatchTimerTick; _homepageLivePatchTimer.Stop(); _homepageLivePatchTimer.Start(); }); } private void _HomepageLivePatchTimerTick(object? sender, EventArgs e) { _homepageLivePatchTimer?.Stop(); _ApplyHomepageLivePatchesFromFile(); } private void _ApplyHomepageLivePatchesFromFile() { if (PanCustom.Children.Count == 0) return; if ((int)Config.Preference.Homepage.Type != 1) return; var file = Path.Combine(_GetHomepageLiveDirectory(), homepageLivePatchFileName); if (!File.Exists(file)) return; try { var token = JsonNode.Parse(_ReadHomepageLivePatchFile(file), new JsonNodeOptions { PropertyNameCaseInsensitive = true }, new JsonDocumentOptions { CommentHandling = JsonCommentHandling.Skip, AllowTrailingCommas = true }); foreach (var patch in _EnumerateHomepageLivePatches(token)) _ApplyHomepageLivePatch(patch); } catch (Exception ex) { ModBase.Log(ex, "[Page] Failed to apply custom homepage live patches", ModBase.LogLevel.Developer); } } private static string _ReadHomepageLivePatchFile(string file) { Exception? lastException = null; for (var i = 0; i < 3; i++) { try { using var stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); using var reader = new StreamReader(stream); return reader.ReadToEnd(); } catch (Exception ex) { lastException = ex; Thread.Sleep(50); } } throw lastException ?? new IOException("Unable to read custom homepage live patch file."); } private static string _GetHomepageLiveDirectory() { return Path.Combine(ModBase.exePath, "PCL"); } private static void _WriteHomepageLiveSupportMarker(string directory) { try { var marker = new JsonObject(new JsonNodeOptions { PropertyNameCaseInsensitive = true }) { ["processId"] = Environment.ProcessId, ["processPath"] = Environment.ProcessPath ?? "", ["patchFile"] = homepageLivePatchFileName, ["startedAt"] = DateTime.Now.ToString("O", CultureInfo.InvariantCulture) }; File.WriteAllText(Path.Combine(directory, homepageLiveSupportFileName), marker.ToJsonString()); } catch (Exception ex) { ModBase.Log(ex, "[Page] Failed to write custom homepage live patch support marker", ModBase.LogLevel.Developer); } } private static void _DeleteHomepageLiveSupportMarker() { try { var file = Path.Combine(_GetHomepageLiveDirectory(), homepageLiveSupportFileName); if (!File.Exists(file)) return; var marker = (JsonObject)JsonNode.Parse(_ReadHomepageLivePatchFile(file), new JsonNodeOptions { PropertyNameCaseInsensitive = true }, new JsonDocumentOptions { CommentHandling = JsonCommentHandling.Skip, AllowTrailingCommas = true })!; if (marker["processId"]?.GetValue() == Environment.ProcessId) File.Delete(file); } catch (Exception ex) { ModBase.Log(ex, "[Page] Failed to delete custom homepage live patch support marker", ModBase.LogLevel.Developer); } } private static IEnumerable _EnumerateHomepageLivePatches(JsonNode token) { if (token is JsonObject obj) { if (obj["patches"] is JsonArray patches) { foreach (var patch in patches.OfType()) yield return patch; yield break; } if (_TryGetString(obj, "target", "tag", "name") != null) { yield return obj; yield break; } foreach (var property in obj) { if (property.Value is not JsonObject patch) continue; patch = (JsonObject)JsonNode.Parse(patch.ToJsonString(), new JsonNodeOptions { PropertyNameCaseInsensitive = true }, new JsonDocumentOptions { CommentHandling = JsonCommentHandling.Skip, AllowTrailingCommas = true })!; patch["target"] ??= property.Key; yield return patch; } } else if (token is JsonArray array) { foreach (var patch in array.OfType()) yield return patch; } } private void _ApplyHomepageLivePatch(JsonObject patch) { var target = _TryGetString(patch, "target", "tag", "name"); if (string.IsNullOrWhiteSpace(target)) return; foreach (var element in _FindElementsByTag(PanCustom, target)) _ApplyHomepageLivePatchToElement(element, patch); } private void _ApplyHomepageLivePatchToElement(FrameworkElement element, JsonObject patch) { _SetPropertyIfPresent(element, patch, "text", "Text"); _SetPropertyIfPresent(element, patch, "title", "Title"); _SetPropertyIfPresent(element, patch, "info", "Info"); _SetPropertyIfPresent(element, patch, "tooltip", "ToolTip"); _SetPropertyIfPresent(element, patch, "toolTip", "ToolTip"); _SetPropertyIfPresent(element, patch, "visibility", "Visibility"); _SetPropertyIfPresent(element, patch, "isEnabled", "IsEnabled"); _SetPropertyIfPresent(element, patch, "opacity", "Opacity"); if (patch["properties"] is JsonObject properties) { foreach (var property in properties) _TrySetElementProperty(element, property.Key, property.Value?.ToString() ?? ""); } var childrenXaml = _TryGetString(patch, "childrenXaml", "ChildrenXaml"); if (!string.IsNullOrEmpty(childrenXaml) && element is Panel panel) _ReplacePanelChildren(panel, childrenXaml); } private static void _SetPropertyIfPresent(FrameworkElement element, JsonObject patch, string jsonName, string propertyName) { if (patch.TryGetPropertyValue(jsonName, out var value)) _TrySetElementProperty(element, propertyName, value?.ToString() ?? ""); } private static bool _TrySetElementProperty(FrameworkElement element, string propertyName, string value) { if (!_homepageLiveAllowedProperties.TryGetValue(propertyName, out var allowedPropertyName)) { ModBase.Log($"[Page] Skipped unsupported live patch property {propertyName}", ModBase.LogLevel.Developer); return false; } propertyName = allowedPropertyName; var property = element.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public); if (property == null || !property.CanWrite) return false; try { var propertyType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType; var trimmedValue = value.Trim(); object convertedValue; if (propertyType == typeof(string)) convertedValue = value; else if (propertyType == typeof(object)) convertedValue = value; else if (propertyType == typeof(bool) && bool.TryParse(trimmedValue, out var boolValue)) convertedValue = boolValue; else if (propertyType == typeof(int) && int.TryParse(trimmedValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue)) convertedValue = intValue; else if (propertyType == typeof(double) && double.TryParse(trimmedValue, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var doubleValue)) convertedValue = doubleValue; else if (propertyType == typeof(Visibility)) { if (!Enum.TryParse(trimmedValue, true, out Visibility visibilityValue)) return false; convertedValue = visibilityValue; } else if (propertyType.IsEnum && Enum.TryParse(propertyType, trimmedValue, true, out var enumValue)) convertedValue = enumValue; else return false; property.SetValue(element, convertedValue); return true; } catch (Exception ex) { ModBase.Log(ex, $"[Page] Failed to set live patch property {propertyName}", ModBase.LogLevel.Developer); return false; } } private static void _ReplacePanelChildren(Panel panel, string childrenXaml) { var content = ModMain.ArgumentReplace(childrenXaml); while (content.Contains("xmlns")) content = content.RegexReplace("xmlns[^\"']*(\"|')[^\"']*(\"|')", "").Replace("xmlns", ""); var wrapped = $"{content}"; if (ModBase.GetObjectFromXML(wrapped) is not Panel parsedPanel) return; var children = parsedPanel.Children.OfType().ToList(); parsedPanel.Children.Clear(); panel.Children.Clear(); foreach (var child in children) panel.Children.Add(child); } private static IEnumerable _FindElementsByTag(DependencyObject root, string tag) { if (root is FrameworkElement element && string.Equals(element.Tag?.ToString(), tag, StringComparison.OrdinalIgnoreCase)) yield return element; int count; try { count = VisualTreeHelper.GetChildrenCount(root); } catch { yield break; } for (var i = 0; i < count; i++) { foreach (var child in _FindElementsByTag(VisualTreeHelper.GetChild(root, i), tag)) yield return child; } } private static string? _TryGetString(JsonObject obj, params string[] names) { foreach (var name in names) { if (obj.TryGetPropertyValue(name, out var value)) return value?.ToString(); } return null; } #endregion }