From 624b37050b26aa2d503aa80d3a29dfbb256b6c2f Mon Sep 17 00:00:00 2001 From: nya-a-cat <188900516+nya-a-cat@users.noreply.github.com> Date: Fri, 14 Aug 2026 12:07:30 +0800 Subject: [PATCH 1/6] =?UTF-8?q?feat(setup):=20=E6=B7=BB=E5=8A=A0=E5=AE=9E?= =?UTF-8?q?=E4=BE=8B=E5=9B=9E=E5=BF=86=E6=96=87=E4=BB=B6=E4=BF=9D=E6=8A=A4?= =?UTF-8?q?=E9=80=89=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PCL.Core/App/Config.cs | 5 +++++ PCL.Core/App/Localization/Languages/en-US.xaml | 2 ++ PCL.Core/App/Localization/Languages/zh-CN.xaml | 2 ++ Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml | 5 +++++ .../Pages/PageSetup/PageSetupLaunch.xaml.cs | 1 + 5 files changed, 15 insertions(+) diff --git a/PCL.Core/App/Config.cs b/PCL.Core/App/Config.cs index 1137f4eaa2..e438acfd3b 100644 --- a/PCL.Core/App/Config.cs +++ b/PCL.Core/App/Config.cs @@ -565,6 +565,11 @@ partial class HideConfigGroup /// [ConfigItem("LaunchArgumentIndieV2", 4, ConfigSource.Local)] public partial int IndieSolutionV2 { get; set; } + /// + /// 删除隔离实例前备份截图与投影原理图。 + /// + [ConfigItem("LaunchArgumentPreservePersonalFiles", true, ConfigSource.Local)] public partial bool PreservePersonalFiles { get; set; } + /// /// 游戏启动后启动器可见性。 /// diff --git a/PCL.Core/App/Localization/Languages/en-US.xaml b/PCL.Core/App/Localization/Languages/en-US.xaml index c090e7a13e..0e0ce51c2d 100644 --- a/PCL.Core/App/Localization/Languages/en-US.xaml +++ b/PCL.Core/App/Localization/Languages/en-US.xaml @@ -2817,6 +2817,8 @@ Isolate snapshots, pre-releases, old versions, and April Fools' versions from other instances. All instances use the same profile. Saves, mods, resource packs, and other files are shared. If multiple instances with mods installed are present, this may cause mod conflicts. When installing a new instance, the isolation option for the new instance will be automatically set accordingly. To adjust the isolation policy for an existing instance, please go to instance settings. + Back up screenshots and schematics when deleting isolated instances + Before deleting an instance, copy its screenshots and schematics folders to PCL\PersonalFiles in the current Minecraft folder. Deletion is canceled if the backup fails. diff --git a/PCL.Core/App/Localization/Languages/zh-CN.xaml b/PCL.Core/App/Localization/Languages/zh-CN.xaml index 9bae29f93c..3146b0aff6 100644 --- a/PCL.Core/App/Localization/Languages/zh-CN.xaml +++ b/PCL.Core/App/Localization/Languages/zh-CN.xaml @@ -2817,6 +2817,8 @@ 将 Minecraft 快照、预发布版、远古版本、愚人节版本与其他实例进行隔离 所有实例均使用同一档案,存档、模组、资源包等均为公用。 若存在多个安装了模组的实例,可能会导致模组冲突。 当安装新实例时,据此自动设置新实例的隔离选项。 若想调整已有实例的隔离策略,请前往它的实例设置。 + 删除隔离实例时备份截图与投影原理图 + 删除实例前,将 screenshots 与 schematics 文件夹复制到当前 Minecraft 文件夹下的 PCL\PersonalFiles。备份失败时会取消删除。 diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml index 12ad782643..6fb6cf2306 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml @@ -161,6 +161,11 @@ + diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.cs b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.cs index b8656e5ca5..8f51c943b8 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.cs +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.cs @@ -54,6 +54,7 @@ public void Reload() TextArgumentTitle.Text = Config.Launch.Title; TextArgumentInfo.Text = Config.Launch.TypeInfo; ComboArgumentIndieV2.SelectedIndex = Config.Launch.IndieSolutionV2; + CheckArgumentPreservePersonalFiles.Checked = Config.Launch.PreservePersonalFiles; ComboArgumentVisibie.SelectedIndex = (int)Config.Launch.LauncherVisibility; ComboArgumentPriority.SelectedValue = ((int)Config.Launch.ProcessPriority).ToString(); ComboArgumentWindowType.SelectedIndex = (int)Config.Launch.GameWindowMode; From 78c09b29d3786270753fb42a2f8c24a39620bed7 Mon Sep 17 00:00:00 2001 From: nya-a-cat <188900516+nya-a-cat@users.noreply.github.com> Date: Fri, 14 Aug 2026 12:10:10 +0800 Subject: [PATCH 2/6] =?UTF-8?q?feat(instance):=20=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E5=89=8D=E5=A4=87=E4=BB=BD=E5=9B=9E=E5=BF=86=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../App/Localization/Languages/en-US.xaml | 3 + .../App/Localization/Languages/zh-CN.xaml | 3 + .../Modules/Minecraft/ModPersonalFiles.cs | 73 +++++++++++++++++++ .../PageInstance/PageInstanceOverall.xaml.cs | 7 +- .../Pages/PageSelectRight.xaml.cs | 6 +- 5 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 Plain Craft Launcher 2/Modules/Minecraft/ModPersonalFiles.cs diff --git a/PCL.Core/App/Localization/Languages/en-US.xaml b/PCL.Core/App/Localization/Languages/en-US.xaml index 0e0ce51c2d..9c01011abb 100644 --- a/PCL.Core/App/Localization/Languages/en-US.xaml +++ b/PCL.Core/App/Localization/Languages/en-US.xaml @@ -977,6 +977,9 @@ Saves folder Shortcuts Unfavorite + Screenshots and schematics will be backed up to PCL\PersonalFiles before deletion. Deletion is canceled if the backup fails. + Failed to back up screenshots or schematics. The instance was kept. See the error details. + Backed up {0} personal file(s) to: {1} diff --git a/PCL.Core/App/Localization/Languages/zh-CN.xaml b/PCL.Core/App/Localization/Languages/zh-CN.xaml index 3146b0aff6..37ac7e2f99 100644 --- a/PCL.Core/App/Localization/Languages/zh-CN.xaml +++ b/PCL.Core/App/Localization/Languages/zh-CN.xaml @@ -977,6 +977,9 @@ 存档文件夹 快捷方式 从收藏夹中移除 + 删除前会将截图与投影原理图备份到 PCL\PersonalFiles;备份失败时会取消删除。 + 备份截图或投影原理图失败,实例未删除。请查看错误详情。 + 已将 {0} 个回忆文件备份到:{1} diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModPersonalFiles.cs b/Plain Craft Launcher 2/Modules/Minecraft/ModPersonalFiles.cs new file mode 100644 index 0000000000..84cb4b129f --- /dev/null +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModPersonalFiles.cs @@ -0,0 +1,73 @@ +using System.IO; +using PCL.Core.App.Localization; +using PCL.Core.UI; + +namespace PCL; + +public static class ModPersonalFiles +{ + public static string ArchiveRoot => + Path.GetFullPath(Path.Combine(ModFolder.mcFolderSelected, "PCL", "PersonalFiles")); + + public static (int FileCount, string TargetFolder) Backup(McInstance instance, + bool includeScreenshots = true, bool includeSchematics = true) + { + var targetFolder = Path.Combine(ArchiveRoot, instance.Name); + var instancePath = Path.GetFullPath(instance.PathInstance); + if (!instancePath.EndsWith(Path.DirectorySeparatorChar)) instancePath += Path.DirectorySeparatorChar; + if (targetFolder.StartsWith(instancePath, StringComparison.OrdinalIgnoreCase)) + throw new InvalidOperationException("回忆文件备份目录不能位于实例目录中"); + + var fileCount = 0; + if (includeScreenshots) + fileCount += _CopyDirectory(Path.Combine(instance.PathIndie, "screenshots"), + Path.Combine(targetFolder, "screenshots")); + if (includeSchematics) + fileCount += _CopyDirectory(Path.Combine(instance.PathIndie, "schematics"), + Path.Combine(targetFolder, "schematics")); + + if (fileCount > 0) + ModBase.Log($"[PersonalFiles] 已备份实例 {instance.Name} 的 {fileCount} 个回忆文件到 {targetFolder}"); + return (fileCount, targetFolder); + } + + public static bool TryBackupBeforeDelete(McInstance instance) + { + try + { + var result = Backup(instance); + if (result.FileCount > 0) + HintService.Hint(Lang.Text("Instance.PersonalFiles.Backup.Success", result.FileCount, + result.TargetFolder), HintType.Success); + return true; + } + catch (Exception ex) + { + ModBase.Log(ex, $"备份实例 {instance.Name} 的回忆文件失败", ModBase.LogLevel.Msgbox, + userSummary: Lang.Text("Instance.PersonalFiles.Backup.Failed")); + return false; + } + } + + private static int _CopyDirectory(string sourceFolder, string targetFolder) + { + if (!Directory.Exists(sourceFolder)) return 0; + var options = new EnumerationOptions + { + RecurseSubdirectories = true, + IgnoreInaccessible = false, + AttributesToSkip = FileAttributes.ReparsePoint + }; + var fileCount = 0; + foreach (var sourceFile in Directory.EnumerateFiles(sourceFolder, "*", options)) + { + var targetFile = Path.Combine(targetFolder, Path.GetRelativePath(sourceFolder, sourceFile)); + ModBase.CopyFile(sourceFile, targetFile); + File.SetCreationTimeUtc(targetFile, File.GetCreationTimeUtc(sourceFile)); + File.SetLastWriteTimeUtc(targetFile, File.GetLastWriteTimeUtc(sourceFile)); + fileCount += 1; + } + + return fileCount; + } +} diff --git a/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceOverall.xaml.cs b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceOverall.xaml.cs index 673becda26..e5513050c9 100644 --- a/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceOverall.xaml.cs +++ b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceOverall.xaml.cs @@ -751,9 +751,12 @@ private void BtnManageDelete_Click(object sender, MouseButtonEventArgs e) (false, true) => "Instance.Overall.Delete.ConfirmMessagePermanent", (false, false) => "Instance.Overall.Delete.ConfirmMessage" }; + var confirmMessage = Lang.Text(confirmMessageKey, PageInstanceLeft.McInstance.Name); + if (isIsolatedInstance && Config.Launch.PreservePersonalFiles) + confirmMessage += "\r\n" + Lang.Text("Instance.PersonalFiles.Backup.DeleteHint"); var confirmResult = ModMain.MyMsgBox( - Lang.Text(confirmMessageKey, PageInstanceLeft.McInstance.Name), + confirmMessage, Lang.Text("Instance.Overall.Delete.ConfirmTitle"), button2: Lang.Text("Common.Action.Cancel"), isWarn: isIsolatedInstance || isShiftPressed @@ -763,6 +766,8 @@ private void BtnManageDelete_Click(object sender, MouseButtonEventArgs e) { case 1: { + if (isIsolatedInstance && Config.Launch.PreservePersonalFiles && + !ModPersonalFiles.TryBackupBeforeDelete(PageInstanceLeft.McInstance)) return; var instancePath = PageInstanceLeft.McInstance.PathInstance; var instanceName = PageInstanceLeft.McInstance.Name; ModBase.IniClearCache(Path.Combine(PageInstanceLeft.McInstance.PathIndie, "options.txt")); diff --git a/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.cs b/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.cs index 8b8ae4149c..6b1b20685e 100644 --- a/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.cs +++ b/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.cs @@ -537,12 +537,16 @@ public static void DeleteVersion(MyListItem item, McInstance mcInstance) ? Lang.Text("Select.Instance.Delete.ConfirmPermanentMessage", mcInstance.Name) : Lang.Text("Select.Instance.Delete.ConfirmMessage", mcInstance.Name); var confirmFullMsg = confirmMsg + - (isHintIndie ? "\r\n" + Lang.Text("Select.Instance.Delete.IsolatedWarning") : ""); + (isHintIndie ? "\r\n" + Lang.Text("Select.Instance.Delete.IsolatedWarning") : ""); + if (isHintIndie && Config.Launch.PreservePersonalFiles) + confirmFullMsg += "\r\n" + Lang.Text("Instance.PersonalFiles.Backup.DeleteHint"); switch (ModMain.MyMsgBox(confirmFullMsg, Lang.Text("Select.Instance.Delete.ConfirmTitle"), button2: Lang.Text("Common.Action.Cancel"), isWarn: true)) { case 1: { + if (isHintIndie && Config.Launch.PreservePersonalFiles && + !ModPersonalFiles.TryBackupBeforeDelete(mcInstance)) return; ModBase.IniClearCache(Path.Combine(mcInstance.PathIndie, "options.txt")); ((DynamicCacheConfigStorage)ConfigService.GetProvider(ConfigSource.GameInstance)).InvalidateCache( mcInstance.PathInstance); From 9ec7b47cac03e37578ac980998a7e821d9823d3a Mon Sep 17 00:00:00 2001 From: nya-a-cat <188900516+nya-a-cat@users.noreply.github.com> Date: Fri, 14 Aug 2026 12:37:39 +0800 Subject: [PATCH 3/6] =?UTF-8?q?fix(instance):=20=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E9=9A=94=E7=A6=BB=E5=AE=9E=E4=BE=8B=E8=B7=AF=E5=BE=84=E5=88=A4?= =?UTF-8?q?=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Plain Craft Launcher 2/Pages/PageSelectRight.xaml.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.cs b/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.cs index 6b1b20685e..96eafca74c 100644 --- a/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.cs +++ b/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.cs @@ -532,7 +532,8 @@ public static void DeleteVersion(MyListItem item, McInstance mcInstance) { var isShiftPressed = Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift); var isHintIndie = mcInstance.state != McInstanceState.Error && - (mcInstance.PathIndie ?? "") != (ModFolder.mcFolderSelected ?? ""); + !string.Equals(mcInstance.PathIndie, ModFolder.mcFolderSelected, + StringComparison.OrdinalIgnoreCase); var confirmMsg = isShiftPressed ? Lang.Text("Select.Instance.Delete.ConfirmPermanentMessage", mcInstance.Name) : Lang.Text("Select.Instance.Delete.ConfirmMessage", mcInstance.Name); From a110d2d91cf45e93ba11535b68559002deb7f995 Mon Sep 17 00:00:00 2001 From: nya-a-cat <188900516+nya-a-cat@users.noreply.github.com> Date: Fri, 14 Aug 2026 12:46:23 +0800 Subject: [PATCH 4/6] =?UTF-8?q?fix(instance):=20=E7=AE=80=E5=8C=96?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=89=8D=E5=A4=87=E4=BB=BD=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PCL.Core/App/Localization/Languages/en-US.xaml | 4 ++-- PCL.Core/App/Localization/Languages/zh-CN.xaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/PCL.Core/App/Localization/Languages/en-US.xaml b/PCL.Core/App/Localization/Languages/en-US.xaml index 9c01011abb..e59147cda0 100644 --- a/PCL.Core/App/Localization/Languages/en-US.xaml +++ b/PCL.Core/App/Localization/Languages/en-US.xaml @@ -977,7 +977,7 @@ Saves folder Shortcuts Unfavorite - Screenshots and schematics will be backed up to PCL\PersonalFiles before deletion. Deletion is canceled if the backup fails. + Files will be backed up to a folder before deletion. Failed to back up screenshots or schematics. The instance was kept. See the error details. Backed up {0} personal file(s) to: {1} @@ -3575,4 +3575,4 @@ Minecraft window loaded: {0} ({1}) Minecraft window maximized: {0} - \ No newline at end of file + diff --git a/PCL.Core/App/Localization/Languages/zh-CN.xaml b/PCL.Core/App/Localization/Languages/zh-CN.xaml index 37ac7e2f99..a3d1deda9f 100644 --- a/PCL.Core/App/Localization/Languages/zh-CN.xaml +++ b/PCL.Core/App/Localization/Languages/zh-CN.xaml @@ -977,7 +977,7 @@ 存档文件夹 快捷方式 从收藏夹中移除 - 删除前会将截图与投影原理图备份到 PCL\PersonalFiles;备份失败时会取消删除。 + 删除前会备份文件到文件夹。 备份截图或投影原理图失败,实例未删除。请查看错误详情。 已将 {0} 个回忆文件备份到:{1} @@ -3575,4 +3575,4 @@ Minecraft 窗口已加载:{0}({1}) 已最大化 Minecraft 窗口:{0} - \ No newline at end of file + From c58e95820b8726b797735e556485778992cfe2c3 Mon Sep 17 00:00:00 2001 From: nya-a-cat <188900516+nya-a-cat@users.noreply.github.com> Date: Fri, 14 Aug 2026 12:58:11 +0800 Subject: [PATCH 5/6] =?UTF-8?q?refactor(instance):=20=E6=94=B6=E7=B4=A7?= =?UTF-8?q?=E5=9B=9E=E5=BF=86=E6=96=87=E4=BB=B6=E5=A4=87=E4=BB=BD=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Modules/Minecraft/ModPersonalFiles.cs | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModPersonalFiles.cs b/Plain Craft Launcher 2/Modules/Minecraft/ModPersonalFiles.cs index 84cb4b129f..b058552a48 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModPersonalFiles.cs +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModPersonalFiles.cs @@ -6,25 +6,21 @@ namespace PCL; public static class ModPersonalFiles { - public static string ArchiveRoot => + private static string _ArchiveRoot => Path.GetFullPath(Path.Combine(ModFolder.mcFolderSelected, "PCL", "PersonalFiles")); - public static (int FileCount, string TargetFolder) Backup(McInstance instance, - bool includeScreenshots = true, bool includeSchematics = true) + private static (int FileCount, string TargetFolder) _Backup(McInstance instance) { - var targetFolder = Path.Combine(ArchiveRoot, instance.Name); + var targetFolder = Path.Combine(_ArchiveRoot, instance.Name); var instancePath = Path.GetFullPath(instance.PathInstance); if (!instancePath.EndsWith(Path.DirectorySeparatorChar)) instancePath += Path.DirectorySeparatorChar; if (targetFolder.StartsWith(instancePath, StringComparison.OrdinalIgnoreCase)) throw new InvalidOperationException("回忆文件备份目录不能位于实例目录中"); - var fileCount = 0; - if (includeScreenshots) - fileCount += _CopyDirectory(Path.Combine(instance.PathIndie, "screenshots"), - Path.Combine(targetFolder, "screenshots")); - if (includeSchematics) - fileCount += _CopyDirectory(Path.Combine(instance.PathIndie, "schematics"), - Path.Combine(targetFolder, "schematics")); + var fileCount = _CopyDirectory(Path.Combine(instance.PathIndie, "screenshots"), + Path.Combine(targetFolder, "screenshots")); + fileCount += _CopyDirectory(Path.Combine(instance.PathIndie, "schematics"), + Path.Combine(targetFolder, "schematics")); if (fileCount > 0) ModBase.Log($"[PersonalFiles] 已备份实例 {instance.Name} 的 {fileCount} 个回忆文件到 {targetFolder}"); @@ -35,7 +31,7 @@ public static bool TryBackupBeforeDelete(McInstance instance) { try { - var result = Backup(instance); + var result = _Backup(instance); if (result.FileCount > 0) HintService.Hint(Lang.Text("Instance.PersonalFiles.Backup.Success", result.FileCount, result.TargetFolder), HintType.Success); From ec82020b517e3891aa507eda05597bcb8325b744 Mon Sep 17 00:00:00 2001 From: nya-a-cat <188900516+nya-a-cat@users.noreply.github.com> Date: Fri, 14 Aug 2026 17:48:05 +0800 Subject: [PATCH 6/6] =?UTF-8?q?feat(instance):=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=89=8D=E5=A4=87=E4=BB=BD=E8=AF=A2=E9=97=AE?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PCL.Core/App/Config.cs | 4 +- PCL.Core/App/ConfigEnums.cs | 10 ++++ .../App/Localization/Languages/en-US.xaml | 11 +++- .../App/Localization/Languages/zh-CN.xaml | 11 +++- .../Modules/Minecraft/ModPersonalFiles.cs | 53 ++++++++++++++++--- .../PageInstance/PageInstanceOverall.xaml.cs | 8 +-- .../Pages/PageSelectRight.xaml.cs | 7 ++- .../Pages/PageSetup/PageSetupLaunch.xaml | 24 +++++++-- .../Pages/PageSetup/PageSetupLaunch.xaml.cs | 2 +- 9 files changed, 102 insertions(+), 28 deletions(-) diff --git a/PCL.Core/App/Config.cs b/PCL.Core/App/Config.cs index e438acfd3b..8f824dc4eb 100644 --- a/PCL.Core/App/Config.cs +++ b/PCL.Core/App/Config.cs @@ -566,9 +566,9 @@ partial class HideConfigGroup [ConfigItem("LaunchArgumentIndieV2", 4, ConfigSource.Local)] public partial int IndieSolutionV2 { get; set; } /// - /// 删除隔离实例前备份截图与投影原理图。 + /// 删除隔离实例前的个人文件备份模式。 /// - [ConfigItem("LaunchArgumentPreservePersonalFiles", true, ConfigSource.Local)] public partial bool PreservePersonalFiles { get; set; } + [ConfigItem("LaunchArgumentPersonalFilesBackupMode", PersonalFilesBackupMode.AskEveryTime, ConfigSource.Local)] public partial PersonalFilesBackupMode PersonalFilesBackup { get; set; } /// /// 游戏启动后启动器可见性。 diff --git a/PCL.Core/App/ConfigEnums.cs b/PCL.Core/App/ConfigEnums.cs index 6b9eef8b29..09c621c1b9 100644 --- a/PCL.Core/App/ConfigEnums.cs +++ b/PCL.Core/App/ConfigEnums.cs @@ -64,6 +64,16 @@ public enum GameProcessPriority RealTime = 4 } +/// +/// 删除实例时的个人文件备份模式。 +/// +public enum PersonalFilesBackupMode +{ + Disabled = 0, + AskEveryTime = 1, + Always = 2 +} + /// /// 游戏启动后启动器可见性 /// diff --git a/PCL.Core/App/Localization/Languages/en-US.xaml b/PCL.Core/App/Localization/Languages/en-US.xaml index e59147cda0..ce1513c7c7 100644 --- a/PCL.Core/App/Localization/Languages/en-US.xaml +++ b/PCL.Core/App/Localization/Languages/en-US.xaml @@ -977,6 +977,11 @@ Saves folder Shortcuts Unfavorite + Back up and delete + Delete without backup + Screenshots were found in instance {0}. Back them up before deletion? Schematics will be backed up at the same time. + Back up screenshots + When screenshots are found, you will be asked whether to back them up before deletion. Files will be backed up to a folder before deletion. Failed to back up screenshots or schematics. The instance was kept. See the error details. Backed up {0} personal file(s) to: {1} @@ -2820,8 +2825,10 @@ Isolate snapshots, pre-releases, old versions, and April Fools' versions from other instances. All instances use the same profile. Saves, mods, resource packs, and other files are shared. If multiple instances with mods installed are present, this may cause mod conflicts. When installing a new instance, the isolation option for the new instance will be automatically set accordingly. To adjust the isolation policy for an existing instance, please go to instance settings. - Back up screenshots and schematics when deleting isolated instances - Before deleting an instance, copy its screenshots and schematics folders to PCL\PersonalFiles in the current Minecraft folder. Deletion is canceled if the backup fails. + Protect screenshots when deleting isolated instances + Always back up + Ask every time + Backups are saved to PCL\PersonalFiles in the current Minecraft folder. Schematics are included when screenshots are backed up. Deletion is canceled if the backup fails. diff --git a/PCL.Core/App/Localization/Languages/zh-CN.xaml b/PCL.Core/App/Localization/Languages/zh-CN.xaml index a3d1deda9f..20f2571c7d 100644 --- a/PCL.Core/App/Localization/Languages/zh-CN.xaml +++ b/PCL.Core/App/Localization/Languages/zh-CN.xaml @@ -977,6 +977,11 @@ 存档文件夹 快捷方式 从收藏夹中移除 + 备份并删除 + 直接删除 + 检测到实例 {0} 中有截图,是否在删除前备份? 投影原理图也会一并备份。 + 备份截图 + 检测到截图时,删除前会询问是否备份。 删除前会备份文件到文件夹。 备份截图或投影原理图失败,实例未删除。请查看错误详情。 已将 {0} 个回忆文件备份到:{1} @@ -2820,8 +2825,10 @@ 将 Minecraft 快照、预发布版、远古版本、愚人节版本与其他实例进行隔离 所有实例均使用同一档案,存档、模组、资源包等均为公用。 若存在多个安装了模组的实例,可能会导致模组冲突。 当安装新实例时,据此自动设置新实例的隔离选项。 若想调整已有实例的隔离策略,请前往它的实例设置。 - 删除隔离实例时备份截图与投影原理图 - 删除实例前,将 screenshots 与 schematics 文件夹复制到当前 Minecraft 文件夹下的 PCL\PersonalFiles。备份失败时会取消删除。 + 删除隔离实例时保护截图 + 始终备份 + 每次询问 + 备份文件会保存到当前 Minecraft 文件夹下的 PCL\PersonalFiles。备份截图时也会保存投影原理图;备份失败时会取消删除。 diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModPersonalFiles.cs b/Plain Craft Launcher 2/Modules/Minecraft/ModPersonalFiles.cs index b058552a48..49c30ee2f2 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModPersonalFiles.cs +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModPersonalFiles.cs @@ -1,4 +1,5 @@ using System.IO; +using PCL.Core.App; using PCL.Core.App.Localization; using PCL.Core.UI; @@ -27,10 +28,35 @@ private static (int FileCount, string TargetFolder) _Backup(McInstance instance) return (fileCount, targetFolder); } - public static bool TryBackupBeforeDelete(McInstance instance) + public static string? GetDeleteHint() + { + return Config.Launch.PersonalFilesBackup switch + { + PersonalFilesBackupMode.Disabled => null, + PersonalFilesBackupMode.AskEveryTime => Lang.Text("Instance.PersonalFiles.Backup.AskHint"), + _ => Lang.Text("Instance.PersonalFiles.Backup.DeleteHint") + }; + } + + public static bool TryHandleBeforeDelete(McInstance instance) { try { + var backupMode = Config.Launch.PersonalFilesBackup; + if (backupMode == PersonalFilesBackupMode.Disabled) return true; + if (backupMode == PersonalFilesBackupMode.AskEveryTime) + { + if (!_HasScreenshots(instance.PathIndie)) return true; + var promptResult = ModMain.MyMsgBox( + Lang.Text("Instance.PersonalFiles.Backup.Ask.Message", instance.Name), + Lang.Text("Instance.PersonalFiles.Backup.Ask.Title"), + Lang.Text("Instance.PersonalFiles.Backup.Ask.BackupAndDelete"), + Lang.Text("Instance.PersonalFiles.Backup.Ask.DeleteWithoutBackup"), + Lang.Text("Common.Action.Cancel")); + if (promptResult == 2) return true; + if (promptResult != 1) return false; + } + var result = _Backup(instance); if (result.FileCount > 0) HintService.Hint(Lang.Text("Instance.PersonalFiles.Backup.Success", result.FileCount, @@ -45,17 +71,18 @@ public static bool TryBackupBeforeDelete(McInstance instance) } } + private static bool _HasScreenshots(string instanceFolder) + { + var screenshotFolder = Path.Combine(instanceFolder, "screenshots"); + return Directory.Exists(screenshotFolder) && + Directory.EnumerateFiles(screenshotFolder, "*", _CreateEnumerationOptions()).Any(); + } + private static int _CopyDirectory(string sourceFolder, string targetFolder) { if (!Directory.Exists(sourceFolder)) return 0; - var options = new EnumerationOptions - { - RecurseSubdirectories = true, - IgnoreInaccessible = false, - AttributesToSkip = FileAttributes.ReparsePoint - }; var fileCount = 0; - foreach (var sourceFile in Directory.EnumerateFiles(sourceFolder, "*", options)) + foreach (var sourceFile in Directory.EnumerateFiles(sourceFolder, "*", _CreateEnumerationOptions())) { var targetFile = Path.Combine(targetFolder, Path.GetRelativePath(sourceFolder, sourceFile)); ModBase.CopyFile(sourceFile, targetFile); @@ -66,4 +93,14 @@ private static int _CopyDirectory(string sourceFolder, string targetFolder) return fileCount; } + + private static EnumerationOptions _CreateEnumerationOptions() + { + return new EnumerationOptions + { + RecurseSubdirectories = true, + IgnoreInaccessible = false, + AttributesToSkip = FileAttributes.ReparsePoint + }; + } } diff --git a/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceOverall.xaml.cs b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceOverall.xaml.cs index e5513050c9..fd15394d0c 100644 --- a/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceOverall.xaml.cs +++ b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceOverall.xaml.cs @@ -752,8 +752,8 @@ private void BtnManageDelete_Click(object sender, MouseButtonEventArgs e) (false, false) => "Instance.Overall.Delete.ConfirmMessage" }; var confirmMessage = Lang.Text(confirmMessageKey, PageInstanceLeft.McInstance.Name); - if (isIsolatedInstance && Config.Launch.PreservePersonalFiles) - confirmMessage += "\r\n" + Lang.Text("Instance.PersonalFiles.Backup.DeleteHint"); + var backupHint = isIsolatedInstance ? ModPersonalFiles.GetDeleteHint() : null; + if (backupHint is not null) confirmMessage += "\r\n" + backupHint; var confirmResult = ModMain.MyMsgBox( confirmMessage, @@ -766,8 +766,8 @@ private void BtnManageDelete_Click(object sender, MouseButtonEventArgs e) { case 1: { - if (isIsolatedInstance && Config.Launch.PreservePersonalFiles && - !ModPersonalFiles.TryBackupBeforeDelete(PageInstanceLeft.McInstance)) return; + if (isIsolatedInstance && + !ModPersonalFiles.TryHandleBeforeDelete(PageInstanceLeft.McInstance)) return; var instancePath = PageInstanceLeft.McInstance.PathInstance; var instanceName = PageInstanceLeft.McInstance.Name; ModBase.IniClearCache(Path.Combine(PageInstanceLeft.McInstance.PathIndie, "options.txt")); diff --git a/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.cs b/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.cs index 96eafca74c..dbdb886d85 100644 --- a/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.cs +++ b/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.cs @@ -539,15 +539,14 @@ public static void DeleteVersion(MyListItem item, McInstance mcInstance) : Lang.Text("Select.Instance.Delete.ConfirmMessage", mcInstance.Name); var confirmFullMsg = confirmMsg + (isHintIndie ? "\r\n" + Lang.Text("Select.Instance.Delete.IsolatedWarning") : ""); - if (isHintIndie && Config.Launch.PreservePersonalFiles) - confirmFullMsg += "\r\n" + Lang.Text("Instance.PersonalFiles.Backup.DeleteHint"); + var backupHint = isHintIndie ? ModPersonalFiles.GetDeleteHint() : null; + if (backupHint is not null) confirmFullMsg += "\r\n" + backupHint; switch (ModMain.MyMsgBox(confirmFullMsg, Lang.Text("Select.Instance.Delete.ConfirmTitle"), button2: Lang.Text("Common.Action.Cancel"), isWarn: true)) { case 1: { - if (isHintIndie && Config.Launch.PreservePersonalFiles && - !ModPersonalFiles.TryBackupBeforeDelete(mcInstance)) return; + if (isHintIndie && !ModPersonalFiles.TryHandleBeforeDelete(mcInstance)) return; ModBase.IniClearCache(Path.Combine(mcInstance.PathIndie, "options.txt")); ((DynamicCacheConfigStorage)ConfigService.GetProvider(ConfigSource.GameInstance)).InvalidateCache( mcInstance.PathInstance); diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml index 6fb6cf2306..37c56f94a0 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml @@ -161,11 +161,25 @@ - + + + + + + + + + + + + + diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.cs b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.cs index 8f51c943b8..80915483b7 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.cs +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.cs @@ -54,7 +54,7 @@ public void Reload() TextArgumentTitle.Text = Config.Launch.Title; TextArgumentInfo.Text = Config.Launch.TypeInfo; ComboArgumentIndieV2.SelectedIndex = Config.Launch.IndieSolutionV2; - CheckArgumentPreservePersonalFiles.Checked = Config.Launch.PreservePersonalFiles; + ComboArgumentPersonalFilesBackup.SelectedIndex = (int)Config.Launch.PersonalFilesBackup; ComboArgumentVisibie.SelectedIndex = (int)Config.Launch.LauncherVisibility; ComboArgumentPriority.SelectedValue = ((int)Config.Launch.ProcessPriority).ToString(); ComboArgumentWindowType.SelectedIndex = (int)Config.Launch.GameWindowMode;