CI / Go Backend (push) Canceled after 0s
初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器 HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块 Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚 AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
1227 lines
51 KiB
C#
1227 lines
51 KiB
C#
using System.IO;
|
||
using System.IO.Compression;
|
||
using System.Windows;
|
||
using System.Windows.Controls;
|
||
using System.Windows.Documents;
|
||
using System.Windows.Input;
|
||
using DotNet.Globbing;
|
||
using PCL.Core.App;
|
||
using PCL.Core.UI;
|
||
using PCL.Core.App.Localization;
|
||
using PCL.Core.Utils;
|
||
|
||
namespace PCL;
|
||
|
||
public class ExportOption : DependencyObject
|
||
{
|
||
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register(
|
||
nameof(Title), typeof(string), typeof(ExportOption)
|
||
);
|
||
|
||
public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register(
|
||
nameof(Description), typeof(string), typeof(ExportOption)
|
||
);
|
||
|
||
public string Title
|
||
{
|
||
get => (string)GetValue(TitleProperty);
|
||
set => SetValue(TitleProperty, value);
|
||
}
|
||
|
||
public string Description
|
||
{
|
||
get => (string)GetValue(DescriptionProperty);
|
||
set => SetValue(DescriptionProperty, value);
|
||
}
|
||
|
||
public string Rules { get; set; }
|
||
|
||
/// <summary>
|
||
/// 如果 Rules 为空,则根据 ShowRules 的内容判断是否应该显示这个复选框。
|
||
/// 如果 ShowRules 也为空,则始终显示。
|
||
/// </summary>
|
||
public string ShowRules { get; set; }
|
||
|
||
public bool DefaultChecked { get; set; }
|
||
public bool RequireModLoader { get; set; }
|
||
public bool RequireOptiFine { get; set; }
|
||
public bool RequireModLoaderOrOptiFine { get; set; }
|
||
}
|
||
|
||
public partial class PageInstanceExport : IRefreshable
|
||
{
|
||
private string currentVersion = "";
|
||
|
||
public PageInstanceExport()
|
||
{
|
||
InitializeComponent();
|
||
Loaded += (_, _) => PageInstanceExport_Loaded();
|
||
CardOptions.MouseLeftButtonDown += CardOptions_MouseLeftButtonDown;
|
||
BtnAdvancedExport.Click += ExportConfig;
|
||
BtnAdvancedImport.Click += ImportConfig;
|
||
BtnExport.Click += StartExport;
|
||
TextExportName.GotFocus += TextExportName_GotFocus;
|
||
CheckAdvancedModrinth.Change += CheckAdvancedModrinth_Change;
|
||
CheckAdvancedInclude.Change += CheckAdvancedInclude_Change;
|
||
}
|
||
|
||
void IRefreshable.Refresh()
|
||
{
|
||
RefreshAll();
|
||
}
|
||
|
||
private void PageInstanceExport_Loaded()
|
||
{
|
||
ModAnimation.AniControlEnabled += 1;
|
||
if ((currentVersion ?? "") != (PageInstanceLeft.McInstance.PathInstance ?? ""))
|
||
RefreshAll(); // 切换到了另一个实例,重置页面
|
||
ModAnimation.AniControlEnabled -= 1;
|
||
}
|
||
|
||
public void RefreshAll()
|
||
{
|
||
ModBase.Log("[Export] 刷新导出页面");
|
||
HintOptiFine.Visibility =
|
||
PageInstanceLeft.McInstance.Info.HasOptiFine ? Visibility.Visible : Visibility.Collapsed;
|
||
currentVersion = PageInstanceLeft.McInstance.PathInstance;
|
||
TextExportName.Text = "";
|
||
TextExportName.HintText = PageInstanceLeft.McInstance.Name;
|
||
TextExportVersion.Text = "";
|
||
TextExportVersion.HintText = "1.0.0";
|
||
CheckAdvancedInclude.Checked = false;
|
||
CheckAdvancedModrinth.Checked = false;
|
||
GetExportOption(CheckOptionsBasic).Description = PageInstanceLeft.McInstance.GetDefaultDescription();
|
||
ResetConfigOverrides();
|
||
ReloadAllSubOptions();
|
||
RefreshAllOptionsUI();
|
||
PanBack.ScrollToHome();
|
||
}
|
||
|
||
// 自动填写整合包名称
|
||
private void TextExportName_GotFocus(object sender, RoutedEventArgs routedEventArgs)
|
||
{
|
||
if (string.IsNullOrEmpty(TextExportName.Text))
|
||
{
|
||
TextExportName.Text = TextExportName.HintText;
|
||
TextExportName.SelectionStart = TextExportName.Text.Length;
|
||
}
|
||
}
|
||
|
||
// 勾选 Modrinth 上传模式时,禁止打包 PCL
|
||
private void CheckAdvancedModrinth_Change(object sender, bool user)
|
||
{
|
||
if (CheckAdvancedModrinth.Checked == true)
|
||
CheckOptionsPcl.Checked = false;
|
||
CheckOptionsPcl.IsEnabled = (bool)!CheckAdvancedModrinth.Checked;
|
||
}
|
||
|
||
// 勾选"其他文件夹"时,同步勾选/取消所有子选项
|
||
private void CheckOptionsOtherFolders_Change(object sender, bool user)
|
||
{
|
||
if (!user) return;
|
||
foreach (var child in PanOptionsOtherFolders.Children)
|
||
if (child is MyCheckBox childBox)
|
||
childBox.Checked = CheckOptionsOtherFolders.Checked;
|
||
}
|
||
|
||
// 勾选打包资源文件时,禁止开启 Modrinth 上传模式
|
||
private void CheckAdvancedInclude_Change(object sender, bool user)
|
||
{
|
||
if (CheckAdvancedInclude.Checked == true)
|
||
CheckAdvancedModrinth.Checked = false;
|
||
CheckAdvancedModrinth.IsEnabled = (bool)!CheckAdvancedInclude.Checked;
|
||
}
|
||
|
||
#region 子选项
|
||
|
||
private readonly string[] subOptionBlackList = new[] { "Quark Programmer Art.zip", "+ EuphoriaPatches_" };
|
||
|
||
/// <summary>
|
||
/// 动态生成子文件夹下的选项,例如资源包、存档等。
|
||
/// </summary>
|
||
private void ReloadAllSubOptions()
|
||
{
|
||
ReloadSubOptions(PanOptionsResourcePacks, true, true, "resourcepacks", "texturepacks");
|
||
ReloadSubOptions(PanOptionsSaves, false, true, "saves");
|
||
ReloadSubOptions(PanOptionsShaderPacks, true, true, "shaderpacks");
|
||
ReloadOtherFolders();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 扫描实例根目录下未被已有选项覆盖的文件夹,生成独立的复选框。
|
||
/// </summary>
|
||
private void ReloadOtherFolders()
|
||
{
|
||
PanOptionsOtherFolders.Children.Clear();
|
||
|
||
var pathIndie = PageInstanceLeft.McInstance.PathIndie;
|
||
var rootDir = new DirectoryInfo(pathIndie);
|
||
if (!rootDir.Exists)
|
||
{
|
||
CheckOptionsOtherFolders.Visibility = Visibility.Collapsed;
|
||
return;
|
||
}
|
||
|
||
var coveredFolders = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||
{
|
||
// 模组
|
||
"mods", "coremods", "lib",
|
||
// 整合包重要数据
|
||
"addons", "multiblocked", "modpack-update-checker", "global_packs",
|
||
"global_resource_packs", "global_data_packs", "optional_data_packs", "maps",
|
||
"mods-resourcepacks", "matmos", "resource_assorts",
|
||
"patchouli_books", "datapacks",
|
||
"openloader", "worldshape", "resources", "scripts", "structures",
|
||
"fontfiles", "oresources", "packmenu", "craftpresence", "pointblanks",
|
||
// 模组设置
|
||
"config", "defaultconfigs", "journeymap", "local", "essential", "gg.essential.mod",
|
||
"CustomSkinLoader",
|
||
// 地图
|
||
"xaero", "XaeroWaypoints", "XaeroWorldMap",
|
||
// 资源包
|
||
"resourcepacks", "texturepacks",
|
||
// 光影
|
||
"shaderpacks",
|
||
// 截图 / 结构 / 录像
|
||
"screenshots", "schematics",
|
||
"replay_recordings", "replay_videos",
|
||
// 存档 / 设置文件夹
|
||
"saves", "configureddefaults",
|
||
// 始终跳过(大量文件或无用缓存)
|
||
"assets", "versions", "libraries", "structureCacheV1",
|
||
".fabric", ".git", "avatar-cache", "cosmetic-cache",
|
||
// PCL 单独处理
|
||
"PCL",
|
||
};
|
||
|
||
var coveredPrefixes = new[] { "kubejs", "template" };
|
||
var coveredSuffixes = new[] { "-natives" };
|
||
|
||
foreach (var subDir in rootDir.EnumerateDirectories())
|
||
{
|
||
if (coveredFolders.Contains(subDir.Name))
|
||
continue;
|
||
if (coveredPrefixes.Any(p => subDir.Name.StartsWithF(p)))
|
||
continue;
|
||
if (coveredSuffixes.Any(s => subDir.Name.EndsWithF(s, true)))
|
||
continue;
|
||
|
||
PanOptionsOtherFolders.Children.Add(new MyCheckBox
|
||
{
|
||
Tag = new ExportOption
|
||
{
|
||
Title = subDir.Name,
|
||
DefaultChecked = false,
|
||
Rules = ModBase.EscapeLikePattern($"{subDir.Name}/")
|
||
}
|
||
});
|
||
}
|
||
|
||
CheckOptionsOtherFolders.Visibility = PanOptionsOtherFolders.Children.Count > 0
|
||
? Visibility.Visible
|
||
: Visibility.Collapsed;
|
||
}
|
||
|
||
private void ReloadSubOptions(StackPanel panel, bool acceptCompressedFile, bool acceptFolder,
|
||
params string[] folders)
|
||
{
|
||
panel.Children.Clear();
|
||
foreach (var Folder in folders)
|
||
{
|
||
var targetFolder = new DirectoryInfo(PageInstanceLeft.McInstance.PathIndie + Folder);
|
||
if (!targetFolder.Exists)
|
||
continue;
|
||
// 查找文件夹下的对应项
|
||
if (acceptCompressedFile)
|
||
foreach (var File in targetFolder.EnumerateFiles("*.zip").Concat(targetFolder.EnumerateFiles("*.rar")))
|
||
{
|
||
if (subOptionBlackList.Any(b => File.Name.ContainsF(b)))
|
||
continue;
|
||
panel.Children.Add(new MyCheckBox
|
||
{
|
||
Tag = new ExportOption
|
||
{
|
||
Title = File.Name, DefaultChecked = true,
|
||
Rules = ModBase.EscapeLikePattern($"{Folder}/{File.Name}")
|
||
}
|
||
});
|
||
if (Folder == "shaderpacks") // 处理光影包的配置文件
|
||
{
|
||
var shaderConfig = new FileInfo(Path.Combine(File.Directory.FullName,
|
||
$"{File.Name}.txt"));
|
||
if (shaderConfig.Exists)
|
||
panel.Children.Add(new MyCheckBox
|
||
{
|
||
Margin = new Thickness(30, 0, 0, 0),
|
||
Tag = new ExportOption
|
||
{
|
||
Title = $"{shaderConfig.Name}", DefaultChecked = true,
|
||
Description = Lang.Text("Instance.Export.Config.ShaderConfigSuffix"),
|
||
Rules = ModBase.EscapeLikePattern($"{Folder}/{shaderConfig.Name}")
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
if (acceptFolder)
|
||
foreach (var SubFolder in targetFolder.EnumerateDirectories().OrderByDescending(f => f.LastWriteTime))
|
||
{
|
||
if (subOptionBlackList.Any(b => SubFolder.Name.ContainsF(b)))
|
||
continue;
|
||
if (!SubFolder.EnumerateFileSystemInfos().Any())
|
||
continue;
|
||
var newCheckBox = new MyCheckBox
|
||
{
|
||
Tag = new ExportOption
|
||
{
|
||
Title = SubFolder.Name, DefaultChecked = true,
|
||
Rules = ModBase.EscapeLikePattern($"{Folder}/{SubFolder.Name}/")
|
||
}
|
||
};
|
||
if (ReferenceEquals(panel, PanOptionsSaves))
|
||
GetExportOption(newCheckBox).Description =
|
||
Lang.Date(SubFolder.LastWriteTime, "g");
|
||
panel.Children.Add(newCheckBox);
|
||
if (Folder == "shaderpacks") // 处理文件夹形式光影包的配置文件
|
||
{
|
||
var shaderConfig = new FileInfo(Path.Combine(targetFolder.FullName,
|
||
$"{SubFolder.Name}.txt"));
|
||
if (shaderConfig.Exists)
|
||
panel.Children.Add(new MyCheckBox
|
||
{
|
||
Margin = new Thickness(30, 0, 0, 0),
|
||
Tag = new ExportOption
|
||
{
|
||
Title = $"{shaderConfig.Name}", DefaultChecked = true,
|
||
Description = Lang.Text("Instance.Export.Config.ShaderConfigSuffix"),
|
||
Rules = ModBase.EscapeLikePattern($"{Folder}/{shaderConfig.Name}")
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 选项
|
||
|
||
/// <summary>
|
||
/// 重新确认是否应该显示每个选项,并将 ExportOption 同步到 UI。
|
||
/// </summary>
|
||
private void RefreshAllOptionsUI()
|
||
{
|
||
// 预先归纳所有至多二级的文件/文件夹
|
||
var allEntries = new List<string>();
|
||
|
||
bool IsValidDirectory(DirectoryInfo folder)
|
||
{
|
||
try
|
||
{
|
||
return folder.Exists && folder.EnumerateFileSystemInfos()
|
||
.Any(i => !subOptionBlackList.Any(b => i.Name.ContainsF(b)));
|
||
}
|
||
catch
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
; // 检查文件夹不为空
|
||
// 一般是由于无法访问,或是一个指向已不存在的文件夹的链接(例如使用 mklink 创造的 resource 文件夹链接)
|
||
var pathInfo = new DirectoryInfo(PageInstanceLeft.McInstance.PathIndie);
|
||
allEntries.AddRange(pathInfo.EnumerateFiles().Select(f => f.Name));
|
||
foreach (var SubFolder in pathInfo.EnumerateDirectories().Where(IsValidDirectory))
|
||
{
|
||
allEntries.Add($@"{SubFolder.Name}\");
|
||
allEntries.AddRange(SubFolder.EnumerateFiles().Select(f => $@"{SubFolder.Name}\{f.Name}"));
|
||
allEntries.AddRange(SubFolder.EnumerateDirectories().Where(IsValidDirectory)
|
||
.Select(d => $@"{SubFolder.Name}\{d.Name}\"));
|
||
}
|
||
|
||
ModBase.Log($"[Export] 共发现 {allEntries.Count} 个可行的二级文件/文件夹");
|
||
|
||
// 确认选项是否应该被显示
|
||
bool IsVisible(ExportOption targetOption)
|
||
{
|
||
// 检查需要 OptiFine 或 Mod 加载器
|
||
if (targetOption.RequireOptiFine && !PageInstanceLeft.McInstance.Info.HasOptiFine)
|
||
return false;
|
||
if (targetOption.RequireModLoader && !PageInstanceLeft.McInstance.Modable)
|
||
return false;
|
||
if (targetOption.RequireModLoaderOrOptiFine && !PageInstanceLeft.McInstance.Info.HasOptiFine &&
|
||
!PageInstanceLeft.McInstance.Modable)
|
||
return false;
|
||
// 粗略检查是否可能有符合规则的文件/文件夹
|
||
return StandardizeLines((targetOption.Rules ?? targetOption.ShowRules).Split('|'), true).Any(rule =>
|
||
{
|
||
if (rule.StartsWithF("!"))
|
||
return false; // 只看正向规则
|
||
// 检查前两级
|
||
try
|
||
{
|
||
if (allEntries.Any(entry => LikeString(entry, rule)))
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ModBase.Log(
|
||
ex,
|
||
$"错误的规则:{rule}",
|
||
ModBase.LogLevel.Hint,
|
||
userSummary: Lang.Text("Instance.Export.Error.OperationFailed"));
|
||
return false;
|
||
}
|
||
|
||
// 粗略检查所有级
|
||
rule = rule.Trim("*?".ToCharArray());
|
||
if (rule.Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries).Count() >= 3)
|
||
{
|
||
if (rule.EndsWithF(@"\"))
|
||
return IsValidDirectory(new DirectoryInfo(PageInstanceLeft.McInstance.PathIndie + rule)); // 文件夹有效
|
||
|
||
return File.Exists(PageInstanceLeft.McInstance.PathIndie + rule);
|
||
// 文件有效
|
||
}
|
||
|
||
return false;
|
||
});
|
||
}
|
||
|
||
;
|
||
// 逐个检查选项
|
||
foreach (var CheckBox in GetAllOptions(true))
|
||
{
|
||
var targetOption = GetExportOption(CheckBox);
|
||
// 名称与简介
|
||
CheckBox.Inlines.Clear();
|
||
CheckBox.Inlines.Add(new Run(targetOption.Title));
|
||
if (!string.IsNullOrEmpty(targetOption.Description))
|
||
CheckBox.Inlines.Add(new Run(" " + targetOption.Description) { Foreground = ThemeManager.colorGray5 });
|
||
// 可见性、默认勾选
|
||
if (string.IsNullOrEmpty(targetOption.Rules) && string.IsNullOrEmpty(targetOption.ShowRules))
|
||
{
|
||
CheckBox.Visibility = Visibility.Visible;
|
||
CheckBox.Checked = targetOption.DefaultChecked;
|
||
}
|
||
else
|
||
{
|
||
var pass = IsVisible(targetOption);
|
||
CheckBox.Visibility = pass ? Visibility.Visible : Visibility.Collapsed;
|
||
CheckBox.Checked = targetOption.DefaultChecked && pass;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 对文本行进行标准化处理,以便使用 Like 进行匹配。
|
||
/// </summary>
|
||
private IEnumerable<string> StandardizeLines(IEnumerable<string> raw, bool addSuffixStarToFolderPath)
|
||
{
|
||
foreach (var IgnoreLineRaw in raw)
|
||
{
|
||
var ignoreLine = IgnoreLineRaw;
|
||
ignoreLine = ignoreLine.Trim();
|
||
if (string.IsNullOrEmpty(ignoreLine) || ignoreLine.StartsWithF("#") || ignoreLine.StartsWithF("="))
|
||
continue;
|
||
ignoreLine = ignoreLine.Replace("/", @"\");
|
||
yield return ignoreLine + (ignoreLine.EndsWithF(@"\") && addSuffixStarToFolderPath ? "**" : "");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取所有可作为选项的 CheckBox。
|
||
/// </summary>
|
||
private IEnumerable<MyCheckBox> GetAllOptions(bool includeHidden)
|
||
{
|
||
foreach (var Element in PanOptions.Children)
|
||
{
|
||
if (!includeHidden &&
|
||
((UIElement)Element).Visibility != Visibility.Visible)
|
||
continue;
|
||
if (Element is MyCheckBox)
|
||
yield return (MyCheckBox)Element;
|
||
else if (Element is StackPanel)
|
||
foreach (var SubElement in ((StackPanel)Element).Children)
|
||
{
|
||
if (!includeHidden && ((UIElement)SubElement).Visibility != Visibility.Visible)
|
||
continue;
|
||
if (SubElement is MyCheckBox)
|
||
yield return (MyCheckBox)SubElement;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取该 CheckBox 对应的 ExportOption。
|
||
/// </summary>
|
||
private ExportOption GetExportOption(MyCheckBox checkBox)
|
||
{
|
||
return (ExportOption)checkBox.Tag;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 配置文件
|
||
|
||
private const string sperator = "==============================================================";
|
||
|
||
// ================ 导出内容段 ================
|
||
|
||
/// <summary>
|
||
/// 从配置文件中读取的规则。
|
||
/// 如果不为 Nothing,则会覆写当前勾选的规则并禁用对应 UI。
|
||
/// </summary>
|
||
private List<string> RulesOverrides
|
||
{
|
||
get => field;
|
||
set
|
||
{
|
||
field = value;
|
||
if (value is null)
|
||
{
|
||
BtnOverrideCancel.Visibility = Visibility.Collapsed;
|
||
PanOptions.Visibility = Visibility.Visible;
|
||
CardOptions.Inlines.Clear();
|
||
CardOptions.Inlines.Add(new Run(Lang.Text("Instance.Export.OptionListTitle")) { FontWeight = FontWeights.Bold });
|
||
}
|
||
else
|
||
{
|
||
BtnOverrideCancel.Visibility = Visibility.Visible;
|
||
PanOptions.Visibility = Visibility.Collapsed;
|
||
CardOptions.Inlines.Clear();
|
||
CardOptions.Inlines.Add(new Run(Lang.Text("Instance.Export.OptionListTitle") + ": ") { FontWeight = FontWeights.Bold });
|
||
CardOptions.Inlines.Add(new Run(Lang.Text("Instance.Export.OptionList.FromConfig")) { FontWeight = FontWeights.Normal });
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前实际生效的所有规则。
|
||
/// </summary>
|
||
private IEnumerable<string> GetAllRules()
|
||
{
|
||
if (RulesOverrides is not null)
|
||
{
|
||
// 返回覆盖的列表
|
||
foreach (var Rule in RulesOverrides)
|
||
yield return Rule;
|
||
}
|
||
else
|
||
{
|
||
// 从当前勾选的所有选项中获取所有规则行
|
||
yield return "";
|
||
yield return "# " + Lang.Text("Instance.Export.Config.Comment.ModifyRules");
|
||
yield return "# " + Lang.Text("Instance.Export.Config.Comment.ReverseMatch");
|
||
yield return "";
|
||
foreach (var CheckBox in GetAllOptions(false))
|
||
{
|
||
if (CheckBox.Checked == false)
|
||
continue;
|
||
var targetOption = GetExportOption(CheckBox);
|
||
if (targetOption.Rules is null)
|
||
continue;
|
||
yield return $"# {targetOption.Title}";
|
||
foreach (var Rule in targetOption.Rules.Split('|'))
|
||
yield return Rule;
|
||
yield return "";
|
||
}
|
||
|
||
yield return "# " + Lang.Text("Instance.Export.Config.Comment.ExcludedFiles");
|
||
yield return "!*.log";
|
||
yield return "!*.dat_old";
|
||
yield return "!*.BakaCoreInfo";
|
||
yield return "!hmclversion.cfg";
|
||
yield return "!log4j2.xml";
|
||
yield return "";
|
||
}
|
||
}
|
||
|
||
// ================ 追加内容段 ================
|
||
|
||
private List<string> extraFiles;
|
||
|
||
/// <summary>
|
||
/// 获取当前实际生效的追加内容。
|
||
/// </summary>
|
||
private IEnumerable<string> GetExtraFileLines()
|
||
{
|
||
if (extraFiles is not null)
|
||
{
|
||
// 返回覆盖的列表
|
||
foreach (var File in extraFiles)
|
||
yield return File;
|
||
}
|
||
else
|
||
{
|
||
// 从当前勾选的所有选项中获取所有规则行
|
||
yield return "";
|
||
yield return "# " + Lang.Text("Instance.Export.Config.Comment.ExtraFiles");
|
||
yield return "# " + Lang.Text("Instance.Export.Config.Comment.ExtraFiles2");
|
||
yield return "";
|
||
}
|
||
}
|
||
|
||
// ================ 重置 ================
|
||
|
||
/// <summary>
|
||
/// 重置配置文件所带来的影响。
|
||
/// </summary>
|
||
private void ResetConfigOverrides()
|
||
{
|
||
RulesOverrides = null;
|
||
configPackPath = null;
|
||
extraFiles = null;
|
||
PanBack.ScrollToHome();
|
||
}
|
||
|
||
private void CardOptions_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||
{
|
||
if (RulesOverrides is null)
|
||
return;
|
||
ResetConfigOverrides();
|
||
}
|
||
|
||
// ================ 保存 / 读取 ================
|
||
|
||
// 保存配置文件
|
||
private void ExportConfig(object sender, MouseButtonEventArgs e)
|
||
{
|
||
try
|
||
{
|
||
var configPath = SystemDialogs.SelectSaveFile(Lang.Text("Instance.Export.SelectFileLocation"), "export_config.txt", Lang.Text("Instance.Export.Config.FileFilter"),
|
||
(string?)States.System.ExportConfigPath);
|
||
if (string.IsNullOrEmpty(configPath))
|
||
return;
|
||
States.System.ExportConfigPath = configPath;
|
||
var configLines = new List<string>();
|
||
// ini 段
|
||
configLines.Add("Name:" + TextExportName.Text);
|
||
configLines.Add("Version:" + TextExportVersion.Text);
|
||
configLines.Add("");
|
||
configLines.Add("# " + Lang.Text("Instance.Export.Config.Comment.IncludeLauncher"));
|
||
configLines.Add("IncludeLauncher:" + CheckOptionsPcl.Checked);
|
||
configLines.Add("");
|
||
configLines.Add("# " + Lang.Text("Instance.Export.Config.Comment.IncludeLauncherCustom"));
|
||
configLines.Add("IncludeLauncherCustom:" + CheckOptionsPclCustom.Checked);
|
||
configLines.Add("");
|
||
configLines.Add("# " + Lang.Text("Instance.Export.Config.Comment.BundleFiles"));
|
||
configLines.Add("# " + Lang.Text("Instance.Export.Config.Comment.BundleFiles2"));
|
||
configLines.Add("# " + Lang.Text("Instance.Export.Config.Comment.BundleFiles3"));
|
||
configLines.Add("DontCheckHostedAssets:" + CheckAdvancedInclude.Checked);
|
||
configLines.Add("");
|
||
configLines.Add("# " + Lang.Text("Instance.Export.Config.Comment.Modrinth"));
|
||
configLines.Add("# " + Lang.Text("Instance.Export.Config.Comment.Modrinth2"));
|
||
configLines.Add("# " + Lang.Text("Instance.Export.Config.Comment.Modrinth3"));
|
||
configLines.Add("ModrinthUploadMode:" + CheckAdvancedModrinth.Checked);
|
||
configLines.Add("");
|
||
configLines.Add("# " + Lang.Text("Instance.Export.Config.Comment.PackPath"));
|
||
configLines.Add("# " + Lang.Text("Instance.Export.Config.Comment.PackPath2"));
|
||
configLines.Add("# " + Lang.Text("Instance.Export.Config.Comment.PackPath3"));
|
||
configLines.Add("PackPath:" + (configPackPath ?? ""));
|
||
configLines.Add("");
|
||
// 导出内容段
|
||
configLines.Add(sperator);
|
||
configLines.AddRange(GetAllRules());
|
||
// 追加内容段
|
||
configLines.Add(sperator);
|
||
configLines.AddRange(GetExtraFileLines());
|
||
// 结束
|
||
ModBase.WriteFile(configPath, configLines.Join("\r\n"));
|
||
HintService.Hint(Lang.Text("Instance.Export.SaveSuccess", configPath), HintType.Success);
|
||
ModBase.OpenExplorer(configPath);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ModBase.Log(
|
||
ex,
|
||
"保存配置失败",
|
||
ModBase.LogLevel.Msgbox,
|
||
userSummary: Lang.Text("Instance.Export.Error.OperationFailed"));
|
||
}
|
||
}
|
||
|
||
#region 配置文件核心读取逻辑
|
||
|
||
/// <summary>
|
||
/// 从指定路径读取配置文件(供按钮和拖放调用)
|
||
/// </summary>
|
||
/// <param name="configPath">配置文件路径</param>
|
||
private void ReadConfigFile(string configPath)
|
||
{
|
||
try
|
||
{
|
||
// 保存配置文件路径到缓存
|
||
States.System.ExportConfigPath = configPath;
|
||
|
||
var fileContent = ModBase.ReadFile(configPath);
|
||
var segments = fileContent.Split(sperator);
|
||
|
||
if (segments.Length == 0)
|
||
{
|
||
HintService.Hint(Lang.Text("Instance.Export.Config.Invalid"), HintType.Error);
|
||
return;
|
||
}
|
||
|
||
// === 解析INI段 ===
|
||
var ini = new Dictionary<string, string>();
|
||
foreach (var LineRaw in segments[0].Split("\r\n".ToCharArray()))
|
||
{
|
||
var line = LineRaw;
|
||
line = line.Trim();
|
||
if (string.IsNullOrEmpty(line) || line.StartsWithF("#") || line.StartsWithF("="))
|
||
continue;
|
||
var index = line.IndexOfF(":");
|
||
if (index > 0) ini[line.Substring(0, index)] = line.Substring(index + 1);
|
||
}
|
||
|
||
// 赋值到界面控件
|
||
TextExportName.Text = ini.GetOrDefault("Name", "");
|
||
TextExportVersion.Text = ini.GetOrDefault("Version", "");
|
||
CheckOptionsPcl.Checked =
|
||
Convert.ToBoolean(ini.GetOrDefault("IncludeLauncher", false.ToString()));
|
||
CheckOptionsPclCustom.Checked =
|
||
Convert.ToBoolean(ini.GetOrDefault("IncludeLauncherCustom", true.ToString()));
|
||
CheckAdvancedModrinth.Checked =
|
||
Convert.ToBoolean(ini.GetOrDefault("ModrinthUploadMode", false.ToString()));
|
||
CheckAdvancedInclude.Checked =
|
||
Convert.ToBoolean(ini.GetOrDefault("DontCheckHostedAssets", false.ToString()));
|
||
configPackPath = ini.GetOrDefault("PackPath");
|
||
|
||
// === 解析导出内容段 ===
|
||
RulesOverrides = segments[1].Replace("\r", "\n")
|
||
.Replace("\n" + "\n", "\n").Split("\n").ToList();
|
||
|
||
// === 解析追加内容段 ===
|
||
if (segments.Length > 2)
|
||
extraFiles = segments[2].Replace("\r", "\n")
|
||
.Replace("\n" + "\n", "\n").Split("\n").ToList();
|
||
else
|
||
extraFiles = null;
|
||
|
||
// 提示成功
|
||
HintService.Hint(Lang.Text("Instance.Export.ReadSuccess", configPath), HintType.Success);
|
||
}
|
||
|
||
catch (Exception ex)
|
||
{
|
||
ModBase.Log(
|
||
ex,
|
||
$"读取配置文件失败:{configPath}",
|
||
ModBase.LogLevel.Msgbox,
|
||
userSummary: Lang.Text("Instance.Export.Error.OperationFailed"));
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
// 读取配置文件
|
||
private void ImportConfig(object sender, MouseButtonEventArgs e)
|
||
{
|
||
try
|
||
{
|
||
var configPath = SystemDialogs.SelectFile(Lang.Text("Instance.Export.Config.FileFilter"), Lang.Text("Instance.Export.SelectConfigFile"),
|
||
(string?)States.System.ExportConfigPath);
|
||
if (string.IsNullOrEmpty(configPath))
|
||
return;
|
||
|
||
// 调用核心读取逻辑
|
||
ReadConfigFile(configPath);
|
||
}
|
||
|
||
catch (Exception ex)
|
||
{
|
||
ModBase.Log(
|
||
ex,
|
||
"选择配置文件失败",
|
||
ModBase.LogLevel.Msgbox,
|
||
userSummary: Lang.Text("Instance.Export.Error.OperationFailed"));
|
||
}
|
||
}
|
||
|
||
#region 拖放事件处理
|
||
|
||
/// <summary>
|
||
/// 文件拖入界面时触发:验证文件类型
|
||
/// </summary>
|
||
private void PanAllBack_DragEnter(object sender, DragEventArgs e)
|
||
{
|
||
// 检查是否包含文件拖放数据
|
||
if (e.Data.GetDataPresent(DataFormats.FileDrop))
|
||
{
|
||
// 获取拖入的文件路径数组
|
||
var files = (string[])e.Data.GetData(DataFormats.FileDrop);
|
||
|
||
// 验证:仅允许单个.txt文件
|
||
if (files.Length == 1 &&
|
||
files[0].EndsWithF(".txt", true))
|
||
e.Effects = DragDropEffects.Copy; // 设置拖放效果为“复制”
|
||
else
|
||
e.Effects = DragDropEffects.None; // 不允许拖放
|
||
}
|
||
else
|
||
{
|
||
e.Effects = DragDropEffects.None;
|
||
}
|
||
|
||
e.Handled = true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 文件放下时触发:读取配置文件
|
||
/// </summary>
|
||
private void PanAllBack_Drop(object sender, DragEventArgs e)
|
||
{
|
||
// 获取拖入的文件路径
|
||
if (e.Data.GetDataPresent(DataFormats.FileDrop))
|
||
{
|
||
var files = (string[])e.Data.GetData(DataFormats.FileDrop);
|
||
var configPath = files[0];
|
||
|
||
// 调用核心读取逻辑
|
||
ReadConfigFile(configPath);
|
||
}
|
||
|
||
e.Handled = true;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#endregion
|
||
|
||
#region 导出
|
||
|
||
/// <summary>
|
||
/// 配置文件中指定的导出位置。
|
||
/// </summary>
|
||
private string configPackPath;
|
||
|
||
/// <summary>
|
||
/// 开始导出。
|
||
/// </summary>
|
||
private void StartExport(object sender, MouseButtonEventArgs e)
|
||
{
|
||
var packName = string.IsNullOrEmpty(TextExportName.Text) ? TextExportName.HintText : TextExportName.Text;
|
||
var packVersion = string.IsNullOrEmpty(TextExportVersion.Text) ? "1.0.0" : TextExportVersion.Text;
|
||
|
||
// 重复任务检查
|
||
var loaderName = Lang.Text("Instance.Export.ExportTask.Prefix") + packName;
|
||
foreach (var OngoingLoader in ModLoader.loaderTaskbar)
|
||
{
|
||
if ((OngoingLoader.name ?? "") != (loaderName ?? ""))
|
||
continue;
|
||
ModMain.frmMain.PageChange(FormMain.PageType.TaskManager);
|
||
return;
|
||
}
|
||
|
||
// 确认导出位置
|
||
string packPath = null;
|
||
if (!string.IsNullOrWhiteSpace(configPackPath) && !configPackPath.EndsWithF(@"\") &&
|
||
!configPackPath.EndsWithF("/"))
|
||
try
|
||
{
|
||
Directory.CreateDirectory(ModBase.GetPathFromFullPath(configPackPath));
|
||
packPath = configPackPath;
|
||
ModBase.Log($"[Export] 使用配置文件中指定的导出路径:{configPackPath}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ModBase.Log(ex, $"无法使用配置文件中指定的导出路径({configPackPath})");
|
||
if (ModMain.MyMsgBox(
|
||
Lang.Text("Instance.Export.PackPathInvalid.WithDetail", configPackPath, ex.ToString()),
|
||
Lang.Text("Instance.Export.PackPathInvalid.Title"), Lang.Text("Common.Action.Confirm"),
|
||
Lang.Text("Common.Action.Cancel")) == 2)
|
||
return;
|
||
}
|
||
|
||
if (packPath is null)
|
||
{
|
||
var extensions = new List<string>();
|
||
if (CheckAdvancedModrinth.Checked == false)
|
||
extensions.Add(Lang.Text("Instance.Export.ZipFilter"));
|
||
if (CheckOptionsPcl.Checked == false)
|
||
extensions.Add(Lang.Text("Instance.Export.MrpackFilter"));
|
||
packPath = SystemDialogs.SelectSaveFile(Lang.Text("Instance.Export.SelectSaveLocation"),
|
||
packName + (string.IsNullOrEmpty(TextExportVersion.Text) ? "" : " " + TextExportVersion.Text),
|
||
extensions.Join("|"));
|
||
ModBase.Log($"[Export] 手动指定的导出路径:{packPath}");
|
||
}
|
||
|
||
if (string.IsNullOrEmpty(packPath))
|
||
return;
|
||
|
||
// 缓存所需参数
|
||
var cacheFolder = ModMain.RequestTaskTempFolder();
|
||
var overridesFolder = Path.Combine(cacheFolder, "modpack", "overrides");
|
||
var mcInstance = PageInstanceLeft.McInstance;
|
||
var pathIndie = mcInstance.PathIndie;
|
||
var checkHostedAssets = (bool)!CheckAdvancedInclude.Checked;
|
||
var modrinthUploadMode = (bool)CheckAdvancedModrinth.Checked;
|
||
var includePCL = (bool)CheckOptionsPcl.Checked;
|
||
var includePCLCustom = (bool)(includePCL ? CheckOptionsPclCustom.Checked : (bool?)false);
|
||
var allRules = StandardizeLines(GetAllRules(), true).ToList();
|
||
var allExtraFiles = StandardizeLines(GetExtraFileLines(), false).ToList();
|
||
ModBase.Log($"[Export] 准备导出整合包,共有 {allRules.Count} 条规则,{allExtraFiles.Count} 条追加内容行");
|
||
|
||
// 构造步骤加载器
|
||
var loaders = new List<ModLoader.LoaderBase>();
|
||
|
||
#region 准备 PCL 文件
|
||
|
||
#if !RELEASE
|
||
if (includePCL)
|
||
loaders.Add(new ModLoader.LoaderTask<int, int>(Lang.Text("Instance.Export.Task.DownloadPclRelease"),
|
||
loader =>
|
||
{
|
||
UpdateManager.DownloadLatestPCL(loader);
|
||
ModBase.CopyFile(Path.Combine(ModBase.pathTemp, "CE-Latest.exe"),
|
||
Path.Combine(cacheFolder, "Plain Craft Launcher.exe"));
|
||
})
|
||
{
|
||
ProgressWeight = 0.5d,
|
||
block = false
|
||
});
|
||
#endif
|
||
|
||
#endregion
|
||
|
||
#region 复制文件
|
||
|
||
loaders.Add(new ModLoader.LoaderTask<int, List<ModLocalComp.LocalCompFile>>(
|
||
Lang.Text("Instance.Export.Task.CopyContent"), loader =>
|
||
{
|
||
loader.output = [];
|
||
// 复制实例文件
|
||
var progress = 0;
|
||
Action<DirectoryInfo> searchFolder = null;
|
||
searchFolder = folder =>
|
||
{
|
||
// 文件夹:进一步搜索
|
||
foreach (var SubFolder in folder.EnumerateDirectories("*", SearchOption.TopDirectoryOnly))
|
||
{
|
||
// 跳过部分又没用文件又多的文件夹,加快搜索
|
||
if ((folder.FullName ?? "") == (pathIndie ?? "") &&
|
||
new[] { "assets", "versions", "libraries" }.Contains(SubFolder.Name))
|
||
continue;
|
||
if (new[] { "structureCacheV1", ".fabric", ".git", "avatar-cache", "cosmetic-cache" }.Contains(
|
||
SubFolder.Name))
|
||
continue;
|
||
searchFolder(SubFolder);
|
||
}
|
||
|
||
// 文件:检查规则并复制
|
||
foreach (var Entry in folder.EnumerateFiles("*", SearchOption.TopDirectoryOnly))
|
||
{
|
||
var relativePath = Entry.FullName.AfterFirst(pathIndie);
|
||
// 检查规则
|
||
var shouldKeep = false;
|
||
foreach (var Rule in allRules)
|
||
{
|
||
var revert = Rule.StartsWith("!");
|
||
if (LikeString(relativePath, Rule.TrimStart('!')))
|
||
shouldKeep = !revert;
|
||
}
|
||
|
||
if (!shouldKeep)
|
||
continue;
|
||
var targetPath = Path.Combine(overridesFolder, relativePath);
|
||
ModBase.CopyFile(Entry.FullName, targetPath);
|
||
// 若为压缩包,考虑联网获取路径
|
||
if (checkHostedAssets &&
|
||
new[] { ".zip", ".rar", ".jar", ".disabled", ".old" }.Contains(Entry.Extension.ToLower()) &&
|
||
new[] { "mods", "packs", "openloader", "resource" }.Any(s => relativePath.Contains(s)))
|
||
{
|
||
var modFile = new ModLocalComp.LocalCompFile(targetPath);
|
||
var unused = modFile.ModrinthHash; // 提前计算 Hash
|
||
unused = modFile.CurseForgeHash.ToString();
|
||
loader.output.Add(modFile);
|
||
}
|
||
|
||
// 更新进度(进度并不准确,主要突出一个我还没似)
|
||
progress += 1;
|
||
if (progress == 25)
|
||
{
|
||
loader.Progress += (0.94d - loader.Progress) * 0.012d;
|
||
progress = 0;
|
||
}
|
||
}
|
||
};
|
||
searchFolder(new DirectoryInfo(pathIndie));
|
||
ModBase.Log($"[Export] 复制 overrides 文件完成,有 {loader.output.Count} 个文件需要联网检查");
|
||
loader.Progress = 0.95d;
|
||
// 复制追加内容到根目录
|
||
var baseFolder = includePCL ? cacheFolder : Path.Combine(cacheFolder, "modpack");
|
||
foreach (var Line in allExtraFiles)
|
||
if (Line.EndsWithF(@"\") || Line.EndsWithF("/"))
|
||
{
|
||
if (Directory.Exists(Line))
|
||
ModBase.CopyDirectory(Line, Path.Combine(baseFolder, ModBase.GetFolderNameFromPath(Line)) + @"\");
|
||
else
|
||
HintService.Hint(Lang.Text("Instance.Export.Config.FolderNotFound", Line), HintType.Error);
|
||
}
|
||
else if (File.Exists(Line))
|
||
{
|
||
ModBase.CopyFile(Line, Path.Combine(baseFolder, ModBase.GetFileNameFromPath(Line)));
|
||
}
|
||
else
|
||
{
|
||
HintService.Hint(Lang.Text("Instance.Export.Config.FileNotFound", Line), HintType.Error);
|
||
}
|
||
|
||
loader.Progress = 0.97d;
|
||
// 复制 PCL 实例设置
|
||
ModBase.CopyDirectory(Path.Combine(mcInstance.PathInstance, "PCL"), Path.Combine(overridesFolder, "PCL"));
|
||
#if RELEASE
|
||
// 复制 PCL 本体
|
||
if (includePCL) ModBase.CopyFile(Basics.ExecutablePath, Path.Combine(cacheFolder, Basics.ExecutableName));
|
||
#endif
|
||
// 复制 PCL 个性化内容
|
||
if (includePCLCustom)
|
||
{
|
||
if (Directory.Exists(Path.Combine(ModBase.exePath, "PCL", "Pictures")))
|
||
ModBase.CopyDirectory(Path.Combine(ModBase.exePath, "PCL", "Pictures"), Path.Combine(cacheFolder, "PCL", "Pictures"));
|
||
if (Directory.Exists(Path.Combine(ModBase.exePath, "PCL", "Musics")))
|
||
ModBase.CopyDirectory(Path.Combine(ModBase.exePath, "PCL", "Musics"), Path.Combine(cacheFolder, "PCL", "Musics"));
|
||
if (File.Exists(Path.Combine(ModBase.exePath, "PCL", "Custom.xaml")))
|
||
ModBase.CopyFile(Path.Combine(ModBase.exePath, "PCL", "Custom.xaml"), Path.Combine(cacheFolder, "PCL", "Custom.xaml"));
|
||
if (File.Exists(Path.Combine(ModBase.exePath, "PCL", "Setup.ini")))
|
||
ModBase.CopyFile(Path.Combine(ModBase.exePath, "PCL", "Setup.ini"), Path.Combine(cacheFolder, "PCL", "Setup.ini"));
|
||
if (File.Exists(Path.Combine(ModBase.exePath, "PCL", "hints.txt")))
|
||
ModBase.CopyFile(Path.Combine(ModBase.exePath, "PCL", "hints.txt"), Path.Combine(cacheFolder, "PCL", "hints.txt"));
|
||
if (File.Exists(Path.Combine(ModBase.exePath, "PCL", "Logo.png")))
|
||
ModBase.CopyFile(Path.Combine(ModBase.exePath, "PCL", "Logo.png"), Path.Combine(cacheFolder, "PCL", "Logo.png"));
|
||
}
|
||
})
|
||
{
|
||
ProgressWeight = 5d
|
||
});
|
||
|
||
#endregion
|
||
|
||
#region 联网检查
|
||
|
||
loaders.Add(
|
||
new ModLoader.LoaderTask<List<ModLocalComp.LocalCompFile>,
|
||
Dictionary<ModLocalComp.LocalCompFile, List<string>>>(Lang.Text("Instance.Export.Task.FetchFileInfo"),
|
||
loader =>
|
||
{
|
||
loader.output = new Dictionary<ModLocalComp.LocalCompFile, List<string>>();
|
||
if (!checkHostedAssets)
|
||
{
|
||
ModBase.Log("[Export] 要求跳过联网获取步骤");
|
||
return;
|
||
}
|
||
|
||
if (!loader.input.Any())
|
||
{
|
||
ModBase.Log("[Export] 没有需要联网检查的文件,跳过联网获取步骤");
|
||
return;
|
||
}
|
||
|
||
// 分平台获取下载地址
|
||
var endedThreadCount = 0;
|
||
var failedExceptions = new List<Exception>();
|
||
|
||
// 从 Modrinth 获取信息
|
||
// 查找对应的文件
|
||
// 写入下载地址
|
||
ModBase.RunInNewThread(() =>
|
||
{
|
||
try
|
||
{
|
||
var modrinthHashes = loader.input.Select(m => m.ModrinthHash);
|
||
var modrinthRaw = (JsonObject)ModBase.GetJson(ModDownload.DlModRequest(
|
||
"https://api.modrinth.com/v2/version_files", "POST",
|
||
$"{{\"hashes\": [\"{modrinthHashes.Join("\",\"")}\"], \"algorithm\": \"sha1\"}}",
|
||
"application/json"));
|
||
foreach (var ModFile in loader.input)
|
||
{
|
||
if (!modrinthRaw.ContainsKey(ModFile.ModrinthHash)) continue;
|
||
if ((string)modrinthRaw[ModFile.ModrinthHash]?["files"]?[0]["hashes"]?["sha1"] !=
|
||
ModFile.ModrinthHash) continue;
|
||
loader.output.AddToList(ModFile,
|
||
(string)modrinthRaw[ModFile.ModrinthHash]["files"][0]["url"]);
|
||
}
|
||
|
||
ModBase.Log($"[Export] 从 Modrinth 获取到 {modrinthRaw.Count} 个本地资源项的对应信息");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ModBase.Log(ex, "从 Modrinth 获取本地 Mod 信息失败");
|
||
failedExceptions.Add(ex);
|
||
}
|
||
finally
|
||
{
|
||
endedThreadCount += 1;
|
||
loader.Progress += 0.45d;
|
||
}
|
||
}, "Modrinth - " + loaderName);
|
||
|
||
// 从 CurseForge 获取信息
|
||
// 查找对应的文件
|
||
// 写入下载地址
|
||
ModBase.RunInNewThread(() =>
|
||
{
|
||
try
|
||
{
|
||
if (modrinthUploadMode) return;
|
||
var curseForgeHashes = loader.input.Select(m => m.CurseForgeHash);
|
||
var curseForgeRaw = (JsonNode)((JsonObject)ModBase.GetJson(
|
||
ModDownload.DlModRequest("https://api.curseforge.com/v1/fingerprints/432/", "POST",
|
||
$"{{\"fingerprints\": [{curseForgeHashes.Join(",")}]}}",
|
||
"application/json")))["data"][
|
||
"exactMatches"];
|
||
foreach (JsonObject ResultJson in curseForgeRaw.AsArray())
|
||
{
|
||
if (!ResultJson.ContainsKey("file")) continue;
|
||
var file = (JsonObject)ResultJson["file"];
|
||
if (string.IsNullOrEmpty((string)file["downloadUrl"])) continue;
|
||
var modFile = loader.input.FirstOrDefault(m =>
|
||
m.CurseForgeHash == file["fileFingerprint"].ToObject<uint>());
|
||
if (modFile is null) continue;
|
||
loader.output.AddToList(modFile,
|
||
ModComp.CompFile.HandleCurseForgeDownloadUrls(file["downloadUrl"].ToString()));
|
||
}
|
||
|
||
ModBase.Log($"[Export] 从 CurseForge 获取到 {curseForgeRaw.AsArray().Count} 个本地资源项的对应信息");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ModBase.Log(ex, "从 CurseForge 获取本地 Mod 信息失败");
|
||
failedExceptions.Add(ex);
|
||
}
|
||
finally
|
||
{
|
||
endedThreadCount += 1;
|
||
loader.Progress += 0.45d;
|
||
}
|
||
}, "CurseForge - " + loaderName); // Modrinth 上传模式下,不能从 CurseForge 获取信息
|
||
|
||
// 等待线程结束
|
||
while (endedThreadCount != 2)
|
||
{
|
||
if (loader.IsAborted)
|
||
return;
|
||
Thread.Sleep(10);
|
||
}
|
||
|
||
// 若失败,确认是否继续
|
||
if (failedExceptions.Count == 1)
|
||
{
|
||
if (ModMain.MyMsgBox(
|
||
Lang.Text("Instance.Export.NetCheckPartialFailed.Message"),
|
||
Lang.Text("Instance.Export.NetCheckPartialFailed.Title"),
|
||
Lang.Text("Common.Action.Continue"), Lang.Text("Common.Action.Cancel")) == 2)
|
||
throw failedExceptions.First();
|
||
}
|
||
else if (failedExceptions.Count > 1)
|
||
{
|
||
if (ModMain.MyMsgBox(
|
||
Lang.Text("Instance.Export.NetCheckAllFailed.Message"),
|
||
Lang.Text("Instance.Export.NetCheckAllFailed.Title"),
|
||
Lang.Text("Common.Action.Continue"), Lang.Text("Common.Action.Cancel")) == 2)
|
||
throw failedExceptions.First();
|
||
}
|
||
})
|
||
{
|
||
show = checkHostedAssets,
|
||
ProgressWeight = checkHostedAssets ? 2d : 0.01d
|
||
});
|
||
|
||
#endregion
|
||
|
||
#region 生成压缩包
|
||
|
||
loaders.Add(new ModLoader.LoaderTask<Dictionary<ModLocalComp.LocalCompFile, List<string>>, int>(
|
||
Lang.Text("Instance.Export.Task.CreateArchive"),
|
||
loader =>
|
||
{
|
||
// 整理文件列表
|
||
var files = new JsonArray();
|
||
foreach (var Pair in loader.input)
|
||
{
|
||
var modFile = Pair.Key;
|
||
files.Add(new JsonObject
|
||
{
|
||
{ "path", Path.GetRelativePath(overridesFolder, modFile.path).Replace(@"\", "/") },
|
||
{
|
||
"hashes",
|
||
new JsonObject
|
||
{
|
||
{ "sha1", modFile.ModrinthHash }, { "sha512", ModBase.GetFileSHA512(modFile.path) }
|
||
}
|
||
},
|
||
{ "downloads", new JsonArray(Pair.Value.OrderByDescending(u => u.Contains("modrinth.com")).Select(s => (JsonNode)s).ToArray()) },
|
||
{ "fileSize", new FileInfo(modFile.path).Length }
|
||
});
|
||
File.Delete(modFile.path);
|
||
}
|
||
|
||
loader.Progress = 0.2d;
|
||
// 导出最终 JSON 文件
|
||
var dependencies = new JsonObject { { "minecraft", mcInstance.Info.VanillaName } };
|
||
if (mcInstance.Info.HasForge)
|
||
dependencies.Add("forge", mcInstance.Info.Forge);
|
||
if (mcInstance.Info.HasFabric)
|
||
dependencies.Add("fabric-loader", mcInstance.Info.Fabric);
|
||
if (mcInstance.Info.HasNeoForge)
|
||
dependencies.Add("neoforge", mcInstance.Info.NeoForge);
|
||
var resultJson = new JsonObject
|
||
{
|
||
{ "game", "minecraft" }, { "formatVersion", 1 }, { "versionId", packVersion }, { "name", packName },
|
||
{ "summary", mcInstance.Desc }, { "files", files }, { "dependencies", dependencies }
|
||
};
|
||
File.WriteAllText(Path.Combine(cacheFolder, "modpack", "modrinth.index.json"),
|
||
resultJson.ToJsonString(new JsonSerializerOptions(JsonCompat.SerializerOptions) { WriteIndented = true }));
|
||
// 打包
|
||
Directory.CreateDirectory(ModBase.GetPathFromFullPath(packPath));
|
||
if (File.Exists(packPath))
|
||
File.Delete(packPath);
|
||
if (includePCL)
|
||
{
|
||
// 首次压缩整合包
|
||
ZipFile.CreateFromDirectory(Path.Combine(cacheFolder, "modpack"), Path.Combine(cacheFolder, "modpack.mrpack"));
|
||
loader.Progress = 0.5d;
|
||
Directory.Delete(Path.Combine(cacheFolder, "modpack"), true);
|
||
loader.Progress = 0.6d;
|
||
// 二次压缩整合包
|
||
ZipFile.CreateFromDirectory(cacheFolder, packPath);
|
||
loader.Progress = 0.9d;
|
||
}
|
||
else
|
||
{
|
||
// 直接压缩整合包
|
||
ZipFile.CreateFromDirectory(Path.Combine(cacheFolder, "modpack"), packPath);
|
||
loader.Progress = 0.8d;
|
||
}
|
||
|
||
Directory.Delete(cacheFolder, true);
|
||
ModBase.OpenExplorer(packPath);
|
||
})
|
||
{
|
||
ProgressWeight = 6d
|
||
});
|
||
|
||
#endregion
|
||
|
||
// 启动
|
||
var mainLoader = new ModLoader.LoaderCombo<string>(loaderName, loaders)
|
||
{ OnStateChanged = ModDownloadLib.LoaderStateChangedHintOnly };
|
||
mainLoader.Start();
|
||
ModLoader.LoaderTaskbarAdd(mainLoader);
|
||
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
|
||
ModMain.frmMain.BtnExtraDownload.Ribble();
|
||
ModMain.frmMain.PageChange(FormMain.PageType.TaskManager);
|
||
}
|
||
|
||
#endregion
|
||
|
||
private static bool LikeString(string input, string pattern)
|
||
{
|
||
pattern = pattern.Replace("#", "[0-9]");
|
||
var options = new GlobOptions { Evaluation = { CaseInsensitive = true } };
|
||
var glob = Glob.Parse(pattern, options);
|
||
return glob.IsMatch(input);
|
||
}
|
||
}
|