using System.IO; using PCL.Core.App; using PCL.Core.App.Configuration; using PCL.Core.App.Localization; using PCL.Core.Utils.OS; namespace PCL { public class CustomEvent { public EventType Type { get; set; } = EventType.None; public string Data { get; set; } = string.Empty; public CustomEvent() { } public CustomEvent(EventType type, string data) { Type = type; Data = data; } public void Raise() => Raise(Type, Data); /// /// 静态入口:根据 EventType 分发给对应的 action 执行。 /// public static void Raise(EventType type, string arg) { if (type == EventType.None) return; ModBase.Log($"[Control] 执行自定义事件: {type}, {arg}"); try { if (ActionMap.TryGetValue(type, out var action)) action(arg, type); else ModMain.MyMsgBox( Lang.Text("Event.Error.UnknownType", type.ToString()), Lang.Text("Event.Error.Title")); } catch (Exception ex) { ModBase.Log( ex, Lang.Text("Event.Error.ExecutionFailed", type, arg), ModBase.LogLevel.Msgbox, userSummary: Lang.Text("Event.Error.ExecutionFailed", type, arg)); } } public static string GetCustomVariable(string name, string defaultValue = "") => States.CustomVariables.GetValueOrDefault(name, defaultValue); /// /// 将 arg 按 '|' 分割为参数数组,空串统一返回 [""]。 /// private static string[] SplitArgs(string arg) => arg.Split('|'); /// /// 将 \\n 替换为 Windows 换行符 \r\n。 /// private static string FixNewlines(string s) => s.Replace("\\n", "\r\n"); /// /// 禁止自定义主页写入的高危设置黑名单 /// private static readonly HashSet SecuritySensitiveSettingKeys = new HashSet(StringComparer.OrdinalIgnoreCase) { "LaunchAdvanceRun", "VersionAdvanceRun" }; /// /// EventType → 执行逻辑 的字典映射,O(1) 分发。 /// private static readonly Dictionary> ActionMap = new() { [EventType.OpenUrl] = _OpenUrl, [EventType.OpenFile] = _OpenFileOrCommand, [EventType.ExecuteCommand] = _OpenFileOrCommand, [EventType.LaunchGame] = _LaunchGame, [EventType.CopyText] = _CopyText, [EventType.RefreshHomepage] = _Refresh, [EventType.RefreshPage] = _Refresh, [EventType.DailyFortune] = _DailyFortune, [EventType.ClearTrash] = _ClearTrash, [EventType.ShowDialog] = _ShowDialog, [EventType.ShowHint] = _ShowHint, [EventType.SwitchPage] = _SwitchPage, [EventType.ImportModpack] = _ModpackInstall, [EventType.InstallModpack] = _ModpackInstall, [EventType.DownloadFile] = _DownloadFile, [EventType.ModifySetting] = _WriteSetting, [EventType.WriteSetting] = _WriteSetting, [EventType.ModifyVariable] = _WriteVariable, [EventType.WriteVariable] = _WriteVariable, [EventType.OpenHelp] = (_, __) => ModBase.OpenWebsite("https://docs.pclc.cc/ce"), }; /// /// 打开网页。校验 https?:// 前缀,非 file 协议。 /// private static void _OpenUrl(string arg, EventType type) { arg = arg.Replace('\\', '/'); if (!arg.Contains("://") || arg.StartsWithF("file", true)) { ModMain.MyMsgBox(Lang.Text("Event.Error.UrlRequired"), Lang.Text("Event.Error.Title")); return; } HintService.Hint(Lang.Text("Event.OpenUrl.Opening", arg)); ModBase.RunInThread(() => ModBase.OpenWebsite(arg)); } /// /// 打开文件 / 执行命令。复用 GetAbsoluteUrls 解析路径后 ProcessInterop.Start 启动。 /// private static void _OpenFileOrCommand(string arg, EventType type) { var args = SplitArgs(arg); ModBase.RunInThread(() => { try { var urls = GetAbsoluteUrls(args[0], type); if (!EventSafetyConfirm($"{urls[0]}{(args.Length >= 2 ? " " + args[1] : "")}")) return; ProcessInterop.Start(urls[0], args.Length >= 2 ? args[1] : ""); } catch (Exception ex) { ModBase.Log( ex, Lang.Text("Event.Error.ExecutionFailed", type, arg), ModBase.LogLevel.Msgbox, userSummary: Lang.Text("Event.Error.ExecutionFailed", type, arg)); } }); } /// /// 启动游戏。支持 \current 指代当前选中实例,可选 ServerIp。 /// private static void _LaunchGame(string arg, EventType type) { var args = SplitArgs(arg); if (args[0] == "\\current") { if (ModInstanceList.McMcInstanceSelected is null) throw new InvalidOperationException(Lang.Text("Event.LaunchGame.SelectVersion")); args[0] = ModInstanceList.McMcInstanceSelected.Name; } ModBase.RunInUi(() => { var launchOptions = new ModLaunch.McLaunchOptions { ServerIp = args.Length >= 2 ? args[1] : null, instance = new McInstance(args[0]) }; if (ModLaunch.McLaunchStart(launchOptions)) HintService.Hint(Lang.Text("Event.LaunchGame.Starting", args[0])); }); } /// /// 复制文本到剪贴板。 /// private static void _CopyText(string arg, EventType _) => ModBase.ClipboardSet(arg); /// /// 刷新主页 / 刷新当前页面。要求当前 pageRight 实现 IRefreshable。 /// private static void _Refresh(string arg, EventType _) { if (ModMain.frmMain?.pageRight is IRefreshable refreshable) { ModBase.RunInUiWait(() => refreshable.Refresh()); if (string.IsNullOrEmpty(arg)) HintService.Hint(Lang.Text("Event.Refresh.Success"), HintType.Success); } else HintService.Hint(Lang.Text("Event.Refresh.NotSupported"), HintType.Error); } /// /// 今日人品。 /// private static void _DailyFortune(string _, EventType __) => PageToolsTest.Jrrp(); /// /// 清理垃圾。异步执行 RubbishClear。 /// private static void _ClearTrash(string _, EventType __) => ModBase.RunInThread(PageToolsTest.RubbishClear); /// /// 弹出消息框。参数:Title|Content[|ButtonText]。 /// private static void _ShowDialog(string arg, EventType type) { var args = SplitArgs(arg); if (args.Length == 1) throw new ArgumentException(Lang.Text("Event.Error.MissingArgs", type.ToString(), "Title|Content")); ModMain.MyMsgBox( FixNewlines(args[1]), FixNewlines(args[0]), args.Length > 2 ? args[2] : Lang.Text("Common.Action.Confirm")); } /// /// 弹出提示条。参数:Message[|HintType](HintType = Info / Success / Error,兼容旧版 Finish / Critical)。 /// private static void _ShowHint(string arg, EventType _) { var args = SplitArgs(arg); var hintType = args.Length == 1 ? HintType.Info : (HintType)Enum.Parse(typeof(HintType), NormalizeHintTypeName(args[1]), true); HintService.Hint(FixNewlines(args[0]), hintType); } /// /// 将旧版 HintType 名称映射到当前名称,保持对旧主页的兼容。 /// private static string NormalizeHintTypeName(string name) => name.ToLowerInvariant() switch { "finish" => nameof(HintType.Success), "critical" => nameof(HintType.Error), _ => name }; /// /// 切换页面。参数:PageType[|PageSubType]。 /// private static void _SwitchPage(string arg, EventType _) { var args = SplitArgs(arg); ModBase.RunInUi(() => { var page = (FormMain.PageType)Enum.Parse(typeof(FormMain.PageType), args[0], true); var sub = args.Length == 1 ? FormMain.PageSubType.Default : (FormMain.PageSubType)Enum.Parse(typeof(FormMain.PageSubType), args[1], true); ModMain.frmMain?.PageChange(page, sub); }); } /// /// 导入 / 安装整合包。触发 ModModpack.ModpackInstall()。 /// private static void _ModpackInstall(string _, EventType __) => ModBase.RunInUi(ModModpack.ModpackInstall); /// /// 下载文件。参数:Url[|SavePath[|FileName]],校验 http/https 前缀并弹安全确认。 /// private static void _DownloadFile(string arg, EventType _) { var args = SplitArgs(arg); args[0] = args[0].Replace('\\', '/'); if (!args[0].StartsWithF("http://", true) && !args[0].StartsWithF("https://", true)) { ModMain.MyMsgBox(Lang.Text("Event.Error.DownloadUrlRequired"), Lang.Text("Event.Error.Title")); return; } if (!EventSafetyConfirm(Lang.Text("Event.Download.Confirm", args[0]))) return; try { PageToolsTest.StartCustomDownload(args[0], args.Length >= 2 ? args[1] : ModBase.GetFileNameFromPath(args[0]), args.Length >= 3 ? args[2] : null); } catch { PageToolsTest.StartCustomDownload(args[0], Lang.Text("Common.State.Unknown")); } } /// /// 写入 / 修改设置。参数:SettingName|Value。 /// private static void _WriteSetting(string arg, EventType type) { var args = SplitArgs(arg); if (args.Length == 1) throw new ArgumentException(Lang.Text("Event.Error.MissingArgs", type.ToString(), "SettingName|Value")); if (ConfigService.TryGetConfigItemNoType(args[0], out var item) && item.Source != ConfigSource.SharedEncrypt) { if (!CanWriteSettingFromCustomEvent(args[0])) return; item.SetValueNoType(args[1], ModInstanceList.McMcInstanceSelected?.PathInstance); } if (args.Length == 2) HintService.Hint(Lang.Text("Event.Setting.Written", args[0], args[1]), HintType.Success); } private static bool CanWriteSettingFromCustomEvent(string key) { if (!SecuritySensitiveSettingKeys.Contains(key)) return true; ModBase.Log($"[Control] 已阻止自定义事件写入高危设置:{key}", ModBase.LogLevel.Developer); HintService.Hint(Lang.Text("Event.Safety.DangerousSettingBlocked", key), HintType.Error); return false; } /// /// 写入 / 修改自定义变量。参数:VariableName|Value。 /// private static void _WriteVariable(string arg, EventType type) { var args = SplitArgs(arg); if (args.Length == 1) throw new ArgumentException(Lang.Text("Event.Error.MissingArgs", type.ToString(), "VariableName|Value")); States.CustomVariables[args[0]] = args[1]; States.CustomVariables = States.CustomVariables; // 触发 ConfigPropertyChanged if (args.Length == 2) HintService.Hint(Lang.Text("Event.Variable.Written", args[0], args[1]), HintType.Success); } public static string[] GetAbsoluteUrls(string relativeUrl, EventType type) { relativeUrl = relativeUrl.Replace('/', '\\').ToLower().TrimStart('\\'); var pclDir = Path.Combine(Basics.ExecutableDirectory, "PCL"); if (relativeUrl.Contains(":\\")) { ModBase.Log($"[Control] 自定义事件中由绝对路径 {type}: {relativeUrl}"); return [relativeUrl, pclDir]; } if (File.Exists(Path.Combine(pclDir, relativeUrl))) { var fullPath = Path.Combine(pclDir, relativeUrl); var resolved = Path.GetFullPath(fullPath); if (!resolved.StartsWith(pclDir, StringComparison.OrdinalIgnoreCase)) throw new UnauthorizedAccessException(Lang.Text("Event.Error.FileNotFound", relativeUrl)); ModBase.Log($"[Control] 自定义事件中由相对 PCL 文件夹的路径 {type}: {fullPath}"); return [fullPath, pclDir]; } if (type is EventType.OpenFile or EventType.ExecuteCommand) { ModBase.Log($"[Control] 自定义事件中直接 {type}: {relativeUrl}"); return [relativeUrl, pclDir]; } throw new FileNotFoundException(Lang.Text("Event.Error.FileNotFound", relativeUrl), relativeUrl); } /// /// 安全确认对话框。已由用户勾选"不再询问"时跳过。 /// private static bool EventSafetyConfirm(string message) { if (States.Hint.HomepageCommand) return true; return ModMain.MyMsgBox( Lang.Text("Event.Safety.ConfirmMessage", message), Lang.Text("Event.Safety.Title"), Lang.Text("Common.Action.Continue"), Lang.Text("Event.Safety.ContinueAlways"), Lang.Text("Common.Action.Cancel")) switch { 1 => true, 2 => States.Hint.HomepageCommand = true, _ => false, }; } } }