diff --git a/MeloongCore b/MeloongCore index 811438a..c774ae4 160000 --- a/MeloongCore +++ b/MeloongCore @@ -1 +1 @@ -Subproject commit 811438aa3c7c0437c7e60e85b516cc593dcf6cb1 +Subproject commit c774ae4c85fd94e0eaa171db21e78a65870df19b diff --git a/PCLCS/Configs.cs b/PCLCS/Configs.cs index 55aaf7b..3de9d93 100644 --- a/PCLCS/Configs.cs +++ b/PCLCS/Configs.cs @@ -1,13 +1,21 @@ namespace PCLCS; public static class Configs { - /// 当前 Java 相关配置的版本。0 代表尚未从老的 Settings 系统中迁移。 + public static readonly DynamicConfigEntry McInstallManifest = new("MinecraftInstallManifest", null); + + // Java + + /// 当前 Java 相关配置的版本。0 代表尚未从老的 Settings 系统中迁移。 public static readonly ConfigEntry JavaConfigVersion = new("JavaConfigVersion", 0); public static readonly ConfigEntry> JavaList = new("JavaList", []); public static readonly ConfigEntry> JavaRemovedList = new("JavaRemovedList", []); public static readonly DynamicConfigEntry JavaMigrated = new("InstanceMigratedJava", false); public static readonly DynamicConfigEntry JavaForced = new("InstanceForcedJava", null); + // 提示 + + public static readonly ConfigEntry HintModifyInstanceWarning = new("HintModifyInstanceWarning", false); + static Configs() { /// 当 JavaForced 获取到一个不在 JavaList 中的值时,将其重置为 null。 JavaForced.PreviewGet += (args, provider) => { @@ -17,4 +25,4 @@ public static class Configs { } }; } -} \ No newline at end of file +} diff --git a/PCLCS/Constants.cs b/PCLCS/Constants.cs index 2e1b7fd..314810e 100644 --- a/PCLCS/Constants.cs +++ b/PCLCS/Constants.cs @@ -68,13 +68,17 @@ public static class Versions { /// /// 版本管理中 Mod 信息缓存的版本号。 /// - public const int LocalModCacheVersion = 19; + public const int LocalModCacheVersion = 21; /// /// Minecraft 本地实例信息缓存的版本号。 /// - public const int McInstanceCacheVersion = 38; + public const int McInstanceCacheVersion = 39; /// /// Java 相关配置的版本号。 /// public const int JavaConfigVersion = 1; + /// + /// Minecraft 安装清单的版本号。 + /// + public const int McInstallManifestVersion = 1; } diff --git a/PCLCS/Java.cs b/PCLCS/Java.cs index 8c88fd3..57d3eef 100644 --- a/PCLCS/Java.cs +++ b/PCLCS/Java.cs @@ -87,9 +87,9 @@ namespace PCLCS; // 相同检查 public bool Equals(Java? other) - => other is not null && string.Equals(Folder, other.Folder, StringComparison.OrdinalIgnoreCase); + => other is not null && PathUtils.IsSame(Folder, other.Folder); public override bool Equals(object? obj) => obj is Java other && Equals(other); - public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Folder); + public override int GetHashCode() => StringComparer.CurrentCultureIgnoreCase.GetHashCode(PathUtils.ForCompare(Folder)); /// 用户友好的描述文本。 public override string ToString() @@ -211,7 +211,7 @@ public static class JavaUtils { } // 结果 var hasReparsePoint = HasReparsePoint(FileUtils.GetInfo(Path.Combine(targetFolder, "java.exe"))); - var isSpecialPath = targetFolder.Contains("java8path_target_") || targetFolder.Contains("javapath_target_") || targetFolder.Contains("javatmp") || targetFolder.Contains("system32"); + var isSpecialPath = targetFolder.ContainsIgnoreCase("java8path_target_") || targetFolder.ContainsIgnoreCase("javapath_target_") || targetFolder.ContainsIgnoreCase("javatmp") || targetFolder.ContainsIgnoreCase("system32"); results.Add((targetFolder, hasReparsePoint, isSpecialPath)); } } catch (UnauthorizedAccessException) { diff --git a/PCLCS/Minecraft/MCUtils.cs b/PCLCS/Minecraft/MCUtils.cs new file mode 100644 index 0000000..641c9a3 --- /dev/null +++ b/PCLCS/Minecraft/MCUtils.cs @@ -0,0 +1,47 @@ +using System.Globalization; + +namespace PCLCS; + +public static class McUtils { + + /// + /// 比较两个版本名;左侧较新返回正数,相同返回 0,右侧较新返回负数。 + /// 无法比较两个预发布版的大小。 + /// 支持的格式:未知版本, 26.1-snapshot-1,1.13.2, 1.7.10-pre4, 1.8_pre, 1.14 Pre-Release 2, 1.14.4 C6 + /// + public static int CompareVersion(string left, string right) { + if (left == right) return 0; + if (left == "未知版本" || right == "未知版本") return left == "未知版本" ? 1 : -1; + left = left.Lower(); + right = right.Lower(); + var leftParts = left.Replace("快照", "snapshot").Replace("预览版", "pre").RegexSearch("[a-z]+|[0-9]+").ToList(); + var rightParts = right.Replace("快照", "snapshot").Replace("预览版", "pre").RegexSearch("[a-z]+|[0-9]+").ToList(); + for (int i = 0; ; i++) { + if (i >= leftParts.Count && i >= rightParts.Count) return string.Compare(left, right, StringComparison.Ordinal); // 两边均缺失,感觉是一个东西 + string leftValue = i < leftParts.Count ? leftParts[i] : "0"; + string rightValue = i < rightParts.Count ? rightParts[i] : "0"; + if (leftValue == rightValue) continue; + int leftNumber = leftValue switch { + "rc" => -1, + "pre" => -2, + "snapshot" => -3, + "experimental" => -4, + _ => int.TryParse(leftValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out int leftResult) ? leftResult : 0 + }; + int rightNumber = rightValue switch { + "rc" => -1, + "pre" => -2, + "snapshot" => -3, + "experimental" => -4, + _ => int.TryParse(rightValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out int rightResult) ? rightResult : 0 + }; + if (leftNumber == 0 && rightNumber == 0) return string.Compare(leftValue, rightValue, StringComparison.Ordinal); + if (leftNumber != rightNumber) return leftNumber.CompareTo(rightNumber); + } + } + /// + /// 比较两个版本名;左侧不旧于右侧返回 True。 + /// + public static bool CompareVersionGE(string left, string right) => CompareVersion(left, right) >= 0; + +} diff --git a/PCLCS/Minecraft/McInstallManifest.cs b/PCLCS/Minecraft/McInstallManifest.cs new file mode 100644 index 0000000..09eefdc --- /dev/null +++ b/PCLCS/Minecraft/McInstallManifest.cs @@ -0,0 +1,71 @@ +namespace PCLCS; + +/// +/// MC 版本安装清单及 PCL 管理文件清单。 +/// +/// 清单格式版本;无版本的旧清单为 0。 +/// 安装目标及受管理组件版本。 +/// 最后修改时间(UTC)。 +/// 受管理文件;没有文件时为空列表。 +public record McInstallManifest(int SchemaVersion, McComponents Components, DateTime LastModifiedUtc, IReadOnlyList ManagedFiles) { + /// + /// 清理以 Mod 形式安装的、已删除或修改的组件的记录,并更新清单。 + /// + public McInstallManifest RefreshModComponents(string gameFolder) { + List invalidFiles = []; + foreach (ManagedFile entry in ManagedFiles) { + if (entry.Component is null) continue; // 不检查非组件文件 + FileInfo file = new(Path.Combine(gameFolder, entry.RelativePath)); + if (!file.Exists || file.LastWriteTimeUtc != entry.LastWriteTimeUtc.ToUniversalTime()) invalidFiles.Add(entry); + } + if (!invalidFiles.Any()) return this; + return this with { + Components = Components with { + OptiFineEntry = invalidFiles.Any(file => file.Component == "OptiFine") ? null : Components.OptiFineEntry, + FabricApiVersion = invalidFiles.Any(file => file.Component == "FabricApi") ? null : Components.FabricApiVersion, + OptiFabricVersion = invalidFiles.Any(file => file.Component == "OptiFabric") ? null : Components.OptiFabricVersion + }, + ManagedFiles = ManagedFiles.Where(file => !invalidFiles.Contains(file)).ToList(), + LastModifiedUtc = DateTime.UtcNow + }; + } + + /// + /// 由 PCL 管理并记录的版本内文件。 + /// + /// 相对于游戏目录的文件路径。确保路径为全小写且可比较。 + /// 小写的 SHA1。 + /// 目标文件的最后修改时间(UTC)。 + /// 安装器组件名;为空时表示文件由整合包管理。 + public record ManagedFile(string RelativePath, string Sha1, DateTime LastWriteTimeUtc, + [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] string? Component = null); +} + +/// 目标 Minecraft 版本。 +/// 目标中各个 Mod Loader 的版本;原版为空字典。 +/// OptiFine 条目信息,不安装时为 。 +/// 由 PCL 管理的 Fabric API 版本;不安装时为 。 +/// 由 PCL 管理的 OptiFabric 版本;不安装时为 。 +[method: JsonConstructor] +public record McComponents(string MinecraftVersion, Dictionary ModLoaderVersions, + [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] JObject? OptiFineEntry = null, + [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] string? FabricApiVersion = null, + [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] string? OptiFabricVersion = null) { + [JsonIgnore] + public string? OptiFineVersion => OptiFineEntry?["InstanceName"]?.Value()?.AfterLast("-OptiFine_"); + public McComponents(string minecraftVersion, JObject? optiFineEntry, string? forgeVersion, + string? neoForgeVersion, string? fabricVersion, string? fabricApiVersion, string? optiFabricVersion, string? liteLoaderVersion) + : this(minecraftVersion, [], optiFineEntry, fabricApiVersion, optiFabricVersion) { + if (forgeVersion is not null) ModLoaderVersions[ModLoaders.Forge] = forgeVersion; + if (neoForgeVersion is not null) ModLoaderVersions[ModLoaders.NeoForge] = neoForgeVersion; + if (fabricVersion is not null) ModLoaderVersions[ModLoaders.Fabric] = fabricVersion; + if (liteLoaderVersion is not null) ModLoaderVersions[ModLoaders.LiteLoader] = liteLoaderVersion; + } + public bool IsEquivalentTo(McComponents other) => + MinecraftVersion == other.MinecraftVersion && + OptiFineVersion == other.OptiFineVersion && + FabricApiVersion == other.FabricApiVersion && + OptiFabricVersion == other.OptiFabricVersion && + ModLoaderVersions.Count == other.ModLoaderVersions.Count && + ModLoaderVersions.All(entry => other.ModLoaderVersions.TryGetValue(entry.Key, out string? version) && version == entry.Value); +} diff --git a/PCLCS/Minecraft/McVersion.cs b/PCLCS/Minecraft/McVersion.cs new file mode 100644 index 0000000..2e5a5e8 --- /dev/null +++ b/PCLCS/Minecraft/McVersion.cs @@ -0,0 +1,295 @@ +using System.Globalization; + +namespace PCLCS; + +/// +/// Minecraft 版本号和组件信息 +/// +public sealed class McVersion { + /// + /// 原版版本名,如 26.1、26.1-snapshot-1、1.12.2、16w01a。 + /// + public string VanillaName { + get => _vanillaName; + set { + _vanillaName = value ?? "Unknown"; + Vanilla = NameToVersion(_vanillaName); + } + } + private string _vanillaName = "Unknown"; + /// + /// 可比较的三段式原版版本号。 + /// 旧格式会将次版本号移到主版本号,例如 1.20.1 会转换为 20.0.1,若没有版本号,例如旧快照,则为 9999.0.0。 + /// + public Version Vanilla = new(9999, 0, 0); + /// 原版版本号是否可靠(不是通过猜测获取)。 + public bool Reliable; + /// 原版版本号是否有效。 + public bool Valid => Vanilla.Major < 1000; + /// 可供比较的原版 Drop 序数,例如 26.3.2 为 263,1.21.5 为 210;若没有版本号,例如旧快照,则直接指定为 209。 + public int Drop => Valid ? Vanilla.Major * 10 + Vanilla.Minor : 209; + /// 已安装的 Mod Loader,可能有多个。未安装时为 。 + public ModLoaders ModLoaders => ModLoaderVersions.Keys.Aggregate(ModLoaders.None, (result, loader) => result | loader); + /// 已安装的各个 Mod Loader 及其版本;无法确定精确版本时为“未知版本”。 + public Dictionary ModLoaderVersions { get; } = []; + /// OptiFine 版本名,如 C8、C9_pre10;未识别到时为 null。 + public string? OptiFine; + /// OptiFine 是否以 Mod 形式安装。 + public bool OptiFineAsMod; + /// 版本的发布时间。 + public DateTime ReleaseTime = new(1970, 1, 1, 15, 0, 0); + /// 是否为愚人节版本。 + public bool IsFool; + /// 是否为快照、预发布版、发布候选或实验性版本。 + public bool IsSnapshot; + /// 是否识别到已安装的 OptiFine。 + public bool HasOptiFine => OptiFine is not null; + /// + /// 可供比较的 OptiFine 版本序数。 + /// + public int OptiFineCode { + get { + if (OptiFine is null or "未知版本" || OptiFine.Length == 0) return 0; + // 字母编号,如 G2 中的 G(7) + int result = char.ToUpperInvariant(OptiFine[0]) - 'A' + 1; + // 末尾数字,如 C5 beta4 中的 5 + result = result * 100 + (int.TryParse(OptiFine.Substring(1).RegexSeek(@"\d+"), out int mainVersion) ? mainVersion : 0); + // 测试标记(正式版为 99,Pre[x] 为 50+x,Beta[x] 为 x) + result *= 100; + string? preview = OptiFine.RegexSeek(@"(?i)(pre|beta)\d*"); + if (preview is null) return result + 99; + int sequence = int.TryParse(preview.RegexSeek(@"\d+"), out int value) ? value : 1; // 为 pre 或 beta 结尾,视作 1 + return result + (preview.StartsWithF("pre", true) ? 50 : 0) + sequence; + } + } + /// + /// 可供比较的类 Forge 版本序数。 + /// + public int ForgelikeCode { + get { + if (!ModLoaderVersions.TryGetValue(ModLoaders.Forge, out string? version)) { + ModLoaderVersions.TryGetValue(ModLoaders.NeoForge, out version); + } + if (version is null or "未知版本") return 0; + // 提取所有数字段,以兼容 beta、alpha 和 snapshot 等非数字后缀。 + int[] segments = version.RegexSearch(@"\d+") + .Select(segment => int.TryParse(segment, out int value) ? value : 0).ToArray(); + return segments.Length switch { + >= 4 => segments[0] * 1000000 + segments[1] * 10000 + segments[3], + 3 => segments[0] * 1000000 + segments[1] * 10000 + segments[2], + 2 => segments[0] * 1000000 + segments[1] * 10000, + 1 => segments[0] * 1000000, + _ => 0 + }; + } + } + + /// + /// 创建默认 Minecraft 版本。 + /// + /// 原版版本名。 + /// 版本名是否来自可靠依据。 + public McVersion(string vanillaName, bool reliable = true) { + Reliable = reliable; + VanillaName = vanillaName; + IsFool = IsFoolName(VanillaName); + IsSnapshot = IsSnapshotName(VanillaName); + } + + /// + /// 通过 Components 创建 Minecraft 版本信息。 + /// + public McVersion(McComponents components) : this(components.MinecraftVersion) { + OptiFine = components.OptiFineVersion?.AfterLast("HD_").AfterFirst("_"); + foreach (var loader in components.ModLoaderVersions) ModLoaderVersions[loader.Key] = loader.Value; + } + + /// + /// 通过 JSON 识别 Minecraft 版本和已安装组件。 + /// + /// 实例文件夹名。 + /// 展开的版本 JSON。 + /// 版本 JAR 中的 version.json。 + public McVersion(string instanceName, JObject json, JObject? jarVersionJson = null) { + string jsonText = json.ToString(Formatting.None); + DateTime releaseTime = new(1970, 1, 1, 15, 0, 0); // 未知版本也可能显示为 1970 年 + try { + if (json["releaseTime"] is JToken releaseTimeToken) releaseTime = releaseTimeToken.ToObject(); + } catch { + } + bool reliable = true; + // 获取游戏版本 + string vanillaName = (GetVanillaName(instanceName, json, jarVersionJson, releaseTime, ref reliable) ?? "Unknown").Replace("_unobfuscated", "").Replace(" Unobfuscated", ""); + Reliable = reliable; + VanillaName = vanillaName; + ReleaseTime = releaseTime; + // 确定版本分类 + string jsonType = json.Value("type") ?? ""; + // OptiFine + OptiFine = jsonText.ContainsIgnoreCase("optifine") ? jsonText.RegexSeek(@"(?<=HD_U_)[^\"":/]+") ?? "未知版本": null; + // 愚人节与快照版本 + DateTime fixedReleaseTime = releaseTime.ToUniversalTime().AddHours(2); + IsFool = jsonType == "fool" || IsFoolName(vanillaName) || (fixedReleaseTime.Month == 4 && fixedReleaseTime.Day == 1 && jsonType == "snapshot"); + IsSnapshot = IsSnapshotName(vanillaName) || instanceName.ContainsIgnoreCase("combat") || jsonType is "snapshot" or "pending" or "fool"; + if (!Reliable) Logger.Info($"无法完全确认 MC 版本号的版本:{VanillaName}"); + // LiteLoader + if (jsonText.ContainsIgnoreCase("liteloader")) { + ModLoaderVersions[ModLoaders.LiteLoader] = jsonText.RegexSeek(@"(?<=com\.mumfrey:liteloader:)[^\"":/]+") ?? "未知版本"; + } + // Fabric、Forge、NeoForge + if (jsonText.ContainsIgnoreCase("net.fabricmc:fabric-loader") || jsonText.ContainsIgnoreCase("org.quiltmc:quilt-loader")) { + ModLoaderVersions[ModLoaders.Fabric] = jsonText.RegexSeek(@"(?<=(net\.fabricmc:fabric-loader:|org\.quiltmc:quilt-loader:))[0-9\.]+(\+build.[0-9]+)?")? + .Replace("+build", "") ?? "未知版本"; + } else if (jsonText.ContainsIgnoreCase("net.neoforge")) { + // 1.20.1 JSON 范例:"--fml.forgeVersion", "47.1.99" + // 1.20.2+ JSON 范例:"--fml.neoForgeVersion", "20.6.119-beta" + ModLoaderVersions[ModLoaders.NeoForge] = FindArgumentValue(json, "--fml.neoForgeVersion") ?? FindArgumentValue(json, "--fml.forgeVersion") ?? "未知版本"; + } else if (jsonText.ContainsIgnoreCase("minecraftforge")) { + ModLoaderVersions[ModLoaders.Forge] = jsonText.RegexSeek(@"(?<=forge:[0-9\.]+(?:_pre[0-9]*)?-)[0-9\.]+") + ?? jsonText.RegexSeek(@"(?<=net\.minecraftforge:minecraftforge:)[0-9\.]+") + ?? jsonText.RegexSeek(@"(?<=net\.minecraftforge:fmlloader:[0-9\.]+-)[0-9\.]+") ?? "未知版本"; + } + } + + private static string? GetVanillaName(string instanceName, JObject json, JObject? jarVersionJson, DateTime releaseTime, ref bool reliable) { + // 获取发布时间并判断是否为老版本 + if (releaseTime.Year is > 2000 and < 2013) return "Old"; + // 实验性快照 + if (json.Value("type") == "pending") return "pending"; + // 从 PCL 下载的版本信息中获取版本号 + if (json["clientVersion"] is JToken clientVersion) return clientVersion.ToString(); + // 从 HMCL 下载的版本信息中获取版本号 + if (json["patches"] is JArray patches) { + JToken? gameVersion = patches.OfType().FirstOrDefault(patch => patch.Value("id") == "game")?["version"]; + if (gameVersion is not null) return gameVersion.ToString(); + } + // 从 Forge / NeoForge Arguments 中获取版本号 + if (FindArgumentValue(json, "--fml.mcVersion") is { Length: > 0 } fmlVersion) return fmlVersion; + // 从继承版本中获取版本号 + string inherit = json["inheritsFrom"]?.ToString() ?? ""; + if (inherit.Length > 0) { + string jar = json["jar"]?.ToString() ?? ""; // LiteLoader 优先使用 Jar + return jar.Length > 0 ? jar : inherit; + } + // 从下载地址中获取版本号 + string? download = json["downloads"]?.ToString(Formatting.None).RegexSeek(@"(?<=launcher.mojang.com/mc/game/)[^/]*"); + if (download is not null) return download; + // 从 Forge/OptiFine/Fabric 版本中获取版本号 + string librariesText = json["libraries"]!.ToString(); + string[] libraryPatterns = [ + @"(?<=net.minecraftforge:forge:)[0-9]{1,2}.[0-9+.]+", + @"(?<=net.minecraftforge:fmlloader:)[0-9]{1,2}.[0-9+.]+", + @"(?<=optifine:OptiFine:)[0-9]{1,2}.[0-9+.]+", + @"(?<=((fabricmc)|(quiltmc)):intermediary:)[^\""]*" + ]; + foreach (string pattern in libraryPatterns) { + if (librariesText.RegexSeek(pattern) is string match) return match; + } + // 从 jar 项中获取版本号 + if (json["jar"] is JToken jarToken) return jarToken.ToString(); + // 从 jar 文件的 version.json 中获取版本号 + if (jarVersionJson?["name"] is JToken jarNameToken && jarNameToken.ToString() is { Length: < 32 } jarName) { // 因为 wiki 说这玩意儿可能是个 hash,虽然我没发现 + Logger.Info($"从版本 jar 中的 version.json 获取到版本号:{jarName}"); + return jarName; + } + string VersionPattern = @"(([1-9][0-9]w[0-9]{2}[a-g])|((1|[2-9][0-9])\.[0-9]+(\.[0-9]+)?(-(pre|rc|snapshot-?)[1-9]*| Pre-Release( [1-9])?)?))(_unobfuscated)?"; + // 从 JSON 的 ID 中获取 + if (json["id"]!.ToString().RegexSeek(VersionPattern, RegexOptions.IgnoreCase) is string idMatch) return idMatch; + // 非准确的版本判断警告 + Logger.Info($"无法完全确认 MC 版本号的版本:{instanceName}"); + reliable = false; + // 从文件夹名中获取 + if (instanceName.RegexSeek(VersionPattern, RegexOptions.IgnoreCase) is string folderMatch) return folderMatch; + // 从 JSON 出现的版本号中获取 + JObject withoutLibraries = (JObject) json.DeepClone(); + withoutLibraries.Remove("libraries"); + return withoutLibraries.ToString(Formatting.None).RegexSeek(VersionPattern, RegexOptions.IgnoreCase); + } + + private static bool IsFoolName(string name) { + name = name.Lower(); + return name.StartsWithF("2.0") || name is "15w14a" or "1.rv-pre1" or + "3d shareware v1.34" or "20w14∞" or "22w13oneblockatatime" or "23w13a_or_b" or + "24w14potato" or "25w14craftmine" or "26w14a" || name.StartsWithF("20w14inf"); + } + + private static bool IsSnapshotName(string name) => + new[] { "w", "snapshot", "rc", "pre", "experimental", "-" }.Any(name.ContainsIgnoreCase); + + /// + /// 读取 arguments 中指定参数的字符串值。 + /// + private static string? FindArgumentValue(JObject json, string name) { + JArray? arguments = json["arguments"]?["game"] as JArray; + if (arguments is null) return null; + for (int i = 0; i + 1 < arguments.Count; i++) { + if (arguments[i]?.ToString() == name) return arguments[i + 1]?.ToString(); + } + return null; + } + + /// + /// 判断版本字符串是否符合 Minecraft 原版格式,例如 1.x、26.x。 + /// + public static bool IsStandardFormat(string? version) { + if (version is null) return false; + if (Regex.IsMatch(version, @"^1\.\d")) return true; + return double.TryParse(version.RegexSeek(@"^[2-9]\d\.\d+"), NumberStyles.Float, + CultureInfo.InvariantCulture, out double value) && value >= 26; + } + + /// + /// 将版本名转换为 Drop 序数;无法转换时返回 209。 + /// + public static int VersionToDrop(string? vanillaName) { + if (vanillaName is null) return 209; + Version version = NameToVersion(vanillaName); + return version.Major >= 1000 ? 209 : version.Major * 10 + version.Minor; + } + + /// + /// 将 Drop 序数转换为版本字符串。 + /// + public static string DropToVersion(int drop) { + if (drop >= 250) return $"{drop / 10}.{drop % 10}"; + if (drop != 209) return $"1.{drop / 10}"; + Logger.Warn(new Exception("尝试将旧快照版 Drop 序数转换为版本字符串,这不应该发生!")); + return "1.20"; + } + + /// + /// 将版本名转换为可比较的三段式原版版本号。 + /// 对老版本格式,例如 1.20.3,会被转换为 20.0.3。 + /// 对愚人节版本,返回其基于的正式版版本号。 + /// 若没有版本号,例如旧快照,则为 9999.0.0。 + /// + public static Version NameToVersion(string? name) { + if (name is null) return new Version(9999, 0, 0); + name = name.Lower().Replace("_unobfuscated", "").Replace(" unobfuscated", ""); + // 愚人节版本 + if (name.StartsWithF("2.0")) return NameToVersion("1.5.1"); + if (name == "15w14a") return NameToVersion("1.8.3"); + if (name.ContainsIgnoreCase(".rv-pre")) return NameToVersion("1.9.2"); + if (name.ContainsIgnoreCase("shareware")) return NameToVersion("1.13.2"); + if (name.StartsWithF("20w14") && name != "20w14a") return NameToVersion("1.15.2"); + if (name.ContainsIgnoreCase("oneblockatatime")) return NameToVersion("1.18.2"); + if (name.ContainsIgnoreCase("23w13a") && name != "20w13a") return NameToVersion("1.19.4"); + if (name == "24w14potato") return NameToVersion("1.20.4"); + if (name == "25w14craftmine") return NameToVersion("1.21.4"); + if (name == "26w14a") return NameToVersion("26.1.1"); + // 其余版本 + static int ParseSegment(string[] segments, int index) { + if (index >= segments.Length) return 0; + // 只读取该段开头连续的数字,忽略其后的预览版标记。 + return int.TryParse(segments[index].RegexSeek(@"^\d+"), out int value) ? value : 0; + } + string[] segments = name.Split([' ', '_', '-', '.'], StringSplitOptions.None); + if (name.StartsWithF("1.")) { + return new Version(ParseSegment(segments, 1), 0, ParseSegment(segments, 2)); + } else if (name.RegexCheck(@"^[2-9][0-9]\.")) { + return new Version(ParseSegment(segments, 0), ParseSegment(segments, 1), ParseSegment(segments, 2)); + } else { + return new Version(9999, 0, 0); + } + } +} diff --git a/PCLCS/Minecraft/OptifineHashes.json b/PCLCS/Minecraft/OptifineHashes.json new file mode 100644 index 0000000..2284b0b --- /dev/null +++ b/PCLCS/Minecraft/OptifineHashes.json @@ -0,0 +1,423 @@ +{ + "f3a3c9d792bdb48595405d52c02cc7baf1db4ecb": "OptiFine_1.7.2_HD_U_E3.jar", + "657169c421ce280f80c73b05ab71ad582d2b37fd": "OptiFine_1.7.2_HD_U_E4.jar", + "99f20d449912cd860b409e4679c54dd8da43d55e": "OptiFine_1.7.2_HD_U_E7.jar", + "8253f6ed418a2a61a612cc9e69fd1fea1d11fa7d": "OptiFine_1.7.2_HD_U_E8.jar", + "379e52fc02cd75562d201b0c505269b6109b2136": "OptiFine_1.7.2_HD_U_F3.jar", + "fb04b91e47b5eaa172d2b76c843e488dba8f909b": "OptiFine_1.7.2_HD_U_F7.jar", + "b4b6c2c99f5734e4267030a47f2eaad2796eb2a7": "OptiFine_1.7.10_HD_U_D3.jar", + "a0b533bc97b24fe771f5b08536c6339d04f92bae": "OptiFine_1.7.10_HD_U_D4.jar", + "5e9327dd05a1e762c23c3c0227445d90f193922f": "OptiFine_1.7.10_HD_U_D6.jar", + "7a7d2adf21038d241dd375faa7316fb2c74d34d3": "OptiFine_1.7.10_HD_U_D7.jar", + "2345677be44dd56a0ae5b90e747a590c3cc00ff8": "OptiFine_1.7.10_HD_U_D8.jar", + "23f133173428aee712eb11f200b461e3114bde31": "OptiFine_1.7.10_HD_U_E3.jar", + "2130d7266224da6a7325051e37444f7e7c19277c": "OptiFine_1.7.10_HD_U_E7.jar", + "55c9c475ecd0f62cc1776ce52be04c5937112936": "OptiFine_1.8.0_HD_U_H5.jar", + "bb2c2997cdc66cfb3c311a3dc065752a4845c1a8": "OptiFine_1.8.0_HD_U_H6.jar", + "692b0b7e878c3416f21b4347fab4516f3cc86dfc": "OptiFine_1.8.0_HD_U_H7.jar", + "5464962b66b9b0dacb3d787e6a71d2365ae50bca": "OptiFine_1.8.0_HD_U_H8.jar", + "b7718b03111bec04036fd3352ce374eea273171c": "OptiFine_1.8.0_HD_U_I3.jar", + "0d1dcf64b4d104a71e8f65a1362a163ed81a9030": "OptiFine_1.8.0_HD_U_I7.jar", + "e5e5eb03748af0fa160b502a28359c0144e254d2": "OptiFine_1.8.8_HD_U_H5.jar", + "9294bc4562a143c35ece03a6b3d3a000109def5f": "OptiFine_1.8.8_HD_U_H6.jar", + "1c06c011904ab22952a96f73d4b575a5c265d207": "OptiFine_1.8.8_HD_U_H7.jar", + "3984e4dd222bb62cb7e28e4d3092029cc7534fd1": "OptiFine_1.8.8_HD_U_H8.jar", + "0dc33a9761ceac19ca0bbb1ea6813c0160d43aa1": "OptiFine_1.8.8_HD_U_I3.jar", + "30cbf4d61052cbfb736a78117da333df23c129a3": "OptiFine_1.8.8_HD_U_I7.jar", + "d8240cfa7387b994fdf2a64bdc8a8860857adc18": "OptiFine_1.8.9_HD_U_H5.jar", + "2636689b31810896cf80493f6fc4d8c1783048ea": "OptiFine_1.8.9_HD_U_H6.jar", + "e4544c9210bc056abffa1986a33c4e72623d4f31": "OptiFine_1.8.9_HD_U_H7.jar", + "4b73738913453bcbca969d3e77f05ae0cf5b8a7e": "OptiFine_1.8.9_HD_U_H8.jar", + "d5cd8b902077075bbf1d865cfd5b3362a6863ace": "OptiFine_1.8.9_HD_U_I3.jar", + "58bec8f8766918dcad2b8412f1fbb6f30cc9319f": "OptiFine_1.8.9_HD_U_I7.jar", + "dcab4849a24ccad5a6a3496c67bc5a88411d2a35": "OptiFine_1.8.9_HD_U_L5.jar", + "d362d58a28f5373b141b9e426e8e160638bfafcd": "OptiFine_1.8.9_HD_U_M5.jar", + "1f9dcc9735e94ebb5195c4014300ea462eb12878": "preview_OptiFine_1.8.9_HD_U_L6_pre1.jar", + "6e0b3b86e51c2647c98e277475ee7e68f32a0d0e": "preview_OptiFine_1.8.9_HD_U_M5_pre2.jar", + "7287fcd1a415ab87eca5e89457cc3507aa3fa579": "preview_OptiFine_1.8.9_HD_U_M6_pre1.jar", + "0bb792be16ec78d8b62586cc4856950befb3391b": "preview_OptiFine_1.8.9_HD_U_M6_pre2.jar", + "fd87c53197312614c34669feb7608f74a30da712": "OptiFine_1.9.0_HD_U_B1.jar", + "f99941037f3633926ffebb3eda67f296a701c019": "OptiFine_1.9.0_HD_U_B2.jar", + "34c47106cda1371ecf13e61aed4c1b53a3a53163": "OptiFine_1.9.0_HD_U_B3.jar", + "ebc7258e13344097588c73a0b48457083069e1df": "OptiFine_1.9.0_HD_U_B5.jar", + "999df92e8904f9268e549135edbec322d332ef62": "OptiFine_1.9.0_HD_U_D7.jar", + "0fe031e95f9c01481c9376d9a1df68f9e9857db8": "OptiFine_1.9.0_HD_U_D8.jar", + "f4bc2eb5e039e842eb07181ea564eba487513228": "OptiFine_1.9.0_HD_U_E3.jar", + "78870a94ec3c4db8aebc4c29d4d39bdda2bf05da": "OptiFine_1.9.0_HD_U_E7.jar", + "d3d55bb37e76d1932cb3ad9dde4b9127993f856d": "OptiFine_1.9.0_HD_U_H5.jar", + "b5faec3b84f42dd396512a3ce7c1b57c0b972b70": "OptiFine_1.9.0_HD_U_I5.jar", + "92742ecac143b1d28c215ca145bd9775a01c5085": "preview_OptiFine_1.9.0_HD_U_I5_pre1.jar", + "c87029938376017deba9108322bcb8424d64e92c": "OptiFine_1.9.2_HD_U_B1.jar", + "d3e5e1fa480e66603ca037d74966a95d25968d51": "OptiFine_1.9.2_HD_U_B2.jar", + "fbd545f921d098235a28f87cda55a0504e831954": "OptiFine_1.9.2_HD_U_B3.jar", + "1ae8e6daeeccb6ee48618e8ccdc0b6276d47944f": "OptiFine_1.9.2_HD_U_D7.jar", + "3350c829b29d170e9ac19809a3ae45cb099277b5": "OptiFine_1.9.2_HD_U_D8.jar", + "c032b2ae0b6087077efef8ae89b612e0d4ea81b7": "OptiFine_1.9.2_HD_U_E3.jar", + "3d8cbc19bcad9e9ecc98f607e10f65673a09b482": "OptiFine_1.9.4_HD_U_B4.jar", + "b6b9e66b34ddcda5294b016602fb17d741ff6f54": "OptiFine_1.9.4_HD_U_B5.jar", + "060ddd5a0b7b7ac5586de04ae53cf923ae5c7290": "OptiFine_1.9.4_HD_U_B6.jar", + "2c39611a89dc583cdb403f402c2efd2bd8c6da63": "OptiFine_1.9.4_HD_U_D7.jar", + "4b26926208de270225eb9d3af24a4ff3841238f5": "OptiFine_1.9.4_HD_U_D8.jar", + "012f82fce41aee5e6e56e551d450a07b2b361646": "OptiFine_1.9.4_HD_U_E3.jar", + "cf9c075fa71dab34cdf1c376a76bd08295c69ce6": "OptiFine_1.9.4_HD_U_E7.jar", + "b3a76b92e0e9594f6fa135f1156b9cc13712db20": "OptiFine_1.9.4_HD_U_H5.jar", + "68a206db0d425dd85f6eba33e52122b01e844c46": "OptiFine_1.9.4_HD_U_I5.jar", + "2e48d20605340cfad844a6587d3848e328fd4399": "preview_OptiFine_1.9.4_HD_U_I5_pre1.jar", + "d7a1e5f40e28fa3283b82bf3af7d3f433b91df08": "OptiFine_1.10_HD_U_B6.jar", + "717a02b21136e022cdb4d7d6ec6a4125f4ed2312": "OptiFine_1.10_HD_U_B7.jar", + "0b5d1719592a57cf414643c5b56f4bb3c23a5b94": "OptiFine_1.10_HD_U_C1.jar", + "236747126edcf0c97fc6a20fa7f6491accbf0108": "OptiFine_1.10_HD_U_D7.jar", + "c5fd78710e821f06f871994032e951065c15c40d": "OptiFine_1.10_HD_U_D8.jar", + "1bea8b0bae04ca4b25c6824a6f23a5442d5aa926": "OptiFine_1.10_HD_U_E3.jar", + "7a7195348ea583458b657573b5fcc72e4adc8308": "OptiFine_1.10_HD_U_E7.jar", + "3bcc46f718711cd6fe3db49de4cc810b75420de8": "OptiFine_1.10_HD_U_H5.jar", + "a0070afe9c1c0ced4bcfcda85359e07917d569bd": "OptiFine_1.10_HD_U_I5.jar", + "f9a36eb0246ad6ba303e670082be2c2515c50dcd": "preview_OptiFine_1.10_HD_U_I5_pre1.jar", + "3ea5dae651b6d64900562d1cde5070ebd9db2936": "OptiFine_1.10.2_HD_U_C1.jar", + "41968442e8d1e30e9a32e7a5594caca22312d9d6": "OptiFine_1.10.2_HD_U_C2.jar", + "9c8bc96d3125199e96f5dbfda685ea5dd92c253e": "OptiFine_1.10.2_HD_U_C3.jar", + "7ec4ccc213b482936baaffb9f1937f7147796893": "OptiFine_1.10.2_HD_U_D1.jar", + "3ee5aecc8e11567fb46de0b94e23cf1428f4c9fc": "OptiFine_1.10.2_HD_U_D2.jar", + "98059ed7e93d0fc058203569ccfe965b022fb18f": "OptiFine_1.10.2_HD_U_D3.jar", + "c3165d0a0207d72e18f4533e7aa5ca30e96819a9": "OptiFine_1.10.2_HD_U_D4.jar", + "347359aef422d64711b470f4cf0803cb072da0fa": "OptiFine_1.10.2_HD_U_D6.jar", + "4ddf7c45e00839031f5793451541bca002f9257f": "OptiFine_1.10.2_HD_U_D7.jar", + "5c3685e279c664a818c41ca213b2bc9bfc4340b0": "OptiFine_1.10.2_HD_U_D8.jar", + "129581af3d7d73b506b42d822cb2d3fce68c8e2e": "OptiFine_1.10.2_HD_U_E3.jar", + "f052d269109154d851cc7a8e766c78802a76e9b2": "OptiFine_1.10.2_HD_U_E7.jar", + "f285cfe6753c5d987e4cb0940da05e9513334d98": "OptiFine_1.10.2_HD_U_H5.jar", + "12fd8dd33150f9e4436f23bac08265305f949e20": "OptiFine_1.10.2_HD_U_I5.jar", + "adac69b25bb95242422a635965c7f17503c694be": "preview_OptiFine_1.10.2_HD_U_I5_pre1.jar", + "06031e4c828b66facefdfa32cd29c585909d9e45": "OptiFine_1.11_HD_U_B1.jar", + "449aef14cee17cf1c79d4bad73b69107202d3c1c": "OptiFine_1.11_HD_U_B2.jar", + "7663e1ee572c742e96a71a355f0775baf7cbc6ef": "OptiFine_1.11_HD_U_B3.jar", + "bde9d34f84a030734c7b9d09eda700c7c828eb96": "OptiFine_1.11_HD_U_B5.jar", + "579cfb75b86f3404c5f6e8f694c51b09b7c9b261": "OptiFine_1.11_HD_U_B6.jar", + "350af22b31dbc27d1b928e7144582fd361deb193": "OptiFine_1.11_HD_U_B7.jar", + "236161183cc5cd5faf84dd27824cc3eaa107ed5f": "OptiFine_1.11_HD_U_B8.jar", + "9ca32d42c7e5ad9de9665ffee9ddd41dddd33b66": "OptiFine_1.11_HD_U_C3.jar", + "2e45de9e9fb385f24c6eecb1a64537de78778105": "OptiFine_1.11_HD_U_C7.jar", + "32df58fec7148e75b599b18691f80e69b7f9cd1b": "OptiFine_1.11_HD_U_F5.jar", + "71057cb7a3d0971c3b56e1fbbc333304a72e1352": "OptiFine_1.11_HD_U_G5.jar", + "c2ee1e1019ab26bee56447f4df8a2639d2029925": "preview_OptiFine_1.11_HD_U_G5_pre1.jar", + "083a002215e2174c0d40af20d2f3844ea349a354": "OptiFine_1.11.2_HD_U_B5.jar", + "eb92ce00048faa95b184d121fa667074532fb9db": "OptiFine_1.11.2_HD_U_B6.jar", + "7449a2db44e86303d588b526dc73127c16b0e94f": "OptiFine_1.11.2_HD_U_B7.jar", + "3e1006c935fe27d83459e77d336460ac5b5c81b4": "OptiFine_1.11.2_HD_U_B8.jar", + "049e7e134e8c7364ab61810d84289394e17921de": "OptiFine_1.11.2_HD_U_B9.jar", + "4871f2edf7ded30c374aa770e9ea4adcc0b786d0": "OptiFine_1.11.2_HD_U_C1.jar", + "e7a54d9e203a1b951f8373ab6dcdedb348e536b1": "OptiFine_1.11.2_HD_U_C2.jar", + "8d7fd0facc2299f3dad9dc7c19cc13f72a780343": "OptiFine_1.11.2_HD_U_C3.jar", + "20db26eaafb1c4ebd99e1dc2fffe4a62456bf60e": "OptiFine_1.11.2_HD_U_C7.jar", + "e4efcf05086389fe18d6fe62eb8ab7512a07323c": "OptiFine_1.11.2_HD_U_F5.jar", + "65c425ab628d1748b489dee8619271402428509e": "OptiFine_1.11.2_HD_U_G5.jar", + "f253807a4689e44da030acd5c2187d2184885e87": "preview_OptiFine_1.11.2_HD_U_G5_pre1.jar", + "1fdacab6f3b0df1f46142acf9c427db2a653d680": "OptiFine_1.12_HD_U_C2.jar", + "a62a02c8437a0dccaf2ea89ab8392fd40dc92eb1": "OptiFine_1.12_HD_U_C3.jar", + "36d6f6f64048f68dd248a4d3c43a6f5e51fbb9c5": "OptiFine_1.12_HD_U_C4.jar", + "fd8e0e3ba43568e5ca0ce31c87373806d269b7e5": "OptiFine_1.12_HD_U_C5.jar", + "25ba634e5105e6d1e615dad0046905b68313cc9f": "OptiFine_1.12_HD_U_C7.jar", + "b9cd2775fc49bf009f82d3ce36235b1e702d46bb": "OptiFine_1.12_HD_U_F5.jar", + "3b8369d33c3257cb247c22b13a41b7db0924e948": "OptiFine_1.12_HD_U_G5.jar", + "aeee885ccf821c34d86537184389873d8c312ae1": "preview_OptiFine_1.12_HD_U_G5_pre1.jar", + "c754771bf6a7371a7084459505c244cbec61a7cf": "OptiFine_1.12.1_HD_U_C5.jar", + "4362a9d38542b5f4ba07782fc4d07949f4fede08": "OptiFine_1.12.1_HD_U_C6.jar", + "38785968beeb796cde55f91995c6cf8bfbc3b062": "OptiFine_1.12.1_HD_U_C7.jar", + "e6f9e6b573119c052062192695fed4ef388db9c1": "OptiFine_1.12.1_HD_U_F5.jar", + "3c9cd74810cad75de5091633a1e0c7d3d76ea09c": "OptiFine_1.12.1_HD_U_G5.jar", + "aaaaff3ae13a0cdffa2475632cf6303b9f86e0b0": "preview_OptiFine_1.12.1_HD_U_G5_pre1.jar", + "a9d21f084a485bc12fce8c9bea1660c6d4b08855": "OptiFine_1.12.2_HD_U_C5.jar", + "2159a0a754828d63446fca7b6f8fa57b710c6048": "OptiFine_1.12.2_HD_U_C6.jar", + "8ed8009fc82b43909190882927f335d5250a0e19": "OptiFine_1.12.2_HD_U_C7.jar", + "a55768a83db4ad0599f68d9bf3fa8041e2ae3852": "OptiFine_1.12.2_HD_U_C8.jar", + "e0236d21a2a164f2af1cfff8f9f325f53c9125f2": "OptiFine_1.12.2_HD_U_C9.jar", + "b91a0d82c24eef95be0af92993b14e94b842f166": "OptiFine_1.12.2_HD_U_D1.jar", + "04c28238506dc3561addb6efb87c9878d5b82ffe": "OptiFine_1.12.2_HD_U_D2.jar", + "b784000951d41f54d67685a0a275d9f61f810514": "OptiFine_1.12.2_HD_U_D3.jar", + "343f814410401ebdbb3314deb17c27cbab67bfac": "OptiFine_1.12.2_HD_U_E1.jar", + "5c87706b6d14061fed248506d8e3f95b7824ad18": "OptiFine_1.12.2_HD_U_E2.jar", + "e805d4be5c2a3343488c573145606e90bb13816d": "OptiFine_1.12.2_HD_U_E3.jar", + "0e8d45666e80829c9ab74527628dace9187fbfb5": "OptiFine_1.12.2_HD_U_F4.jar", + "84ada0f48bcfb16c37c49283627597bb74d935f6": "OptiFine_1.12.2_HD_U_F5.jar", + "ca3aea3a09ce215906c346fe190907fe0347b0c4": "OptiFine_1.12.2_HD_U_G5.jar", + "23a8be042f354ec97c99672906c3e61fdf54ca2c": "preview_OptiFine_1.12.2_HD_U_F6_pre1.jar", + "b71f993a364c7945081cc6cde9a20d29b4e43f1f": "preview_OptiFine_1.12.2_HD_U_G5_pre1.jar", + "ab2118b1ed23444d6decf081e0295e4c679af6c2": "preview_OptiFine_1.12.2_HD_U_G6_pre1.jar", + "25498de48dd9b7ed2ba5b83464950ad132e61c1f": "preview_OptiFine_1.14_HD_U_F1_pre6.jar", + "396ce7f0e22aae3543134baabd61ad547334c0aa": "OptiFine_1.14.2_HD_U_F1.jar", + "c8c6da3e2ae32d020d9001d4489aad702680f50f": "preview_OptiFine_1.14.2_HD_U_F1_pre15.jar", + "52420ef34128a8b15d7c8f5ed8f7ebc94b708efd": "OptiFine_1.14.3_HD_U_F1.jar", + "887d648f26f7ba17ea34c0c2dcff4096f7a226b1": "OptiFine_1.14.3_HD_U_F2.jar", + "ad8a8f60931f48e2873238d1460268bb4111f6c9": "OptiFine_1.14.4_HD_U_F2.jar", + "c9710081fb7012ddb56838ac54653ac2258a2404": "OptiFine_1.14.4_HD_U_F3.jar", + "c09f4d1ad66df8e3039334023422f1bc8cc211b8": "OptiFine_1.14.4_HD_U_F4.jar", + "3408258990309de773cc5a8ab71cb4137718a82b": "OptiFine_1.14.4_HD_U_F5.jar", + "117c01a5431244a8b6a3ac393374dac9624f9be6": "OptiFine_1.14.4_HD_U_G5.jar", + "ac602fe6a39d2fb4cadfe08e87af0b0daa2d8ac9": "preview_OptiFine_1.14.4_HD_U_G5_pre1.jar", + "630a32be9b711b5a479507159d5d2598189fed33": "OptiFine_1.15.2_HD_U_G6.jar", + "8339858b33056ec8975f6c0c9e1694a11f0d209a": "preview_OptiFine_1.15.2_HD_U_G1_pre1.jar", + "6cdf96d972b5f70c4575ff875a031c4931985686": "preview_OptiFine_1.15.2_HD_U_G1_pre10.jar", + "489d2284f2d50bd7c7cf4c216a766503d37e1f3c": "preview_OptiFine_1.15.2_HD_U_G1_pre12.jar", + "c66d412c4cb46c6f2c76c224dc45a62d30ee8b43": "preview_OptiFine_1.15.2_HD_U_G1_pre13.jar", + "582eb1d6470cc133db563bd1c2c8bc2068215806": "preview_OptiFine_1.15.2_HD_U_G1_pre18.jar", + "64d738862683c54a22e1856b2c13379599ca79ce": "preview_OptiFine_1.15.2_HD_U_G1_pre19.jar", + "3da9d466c847c50a73dc634dabc6a57f3b24e2f7": "preview_OptiFine_1.15.2_HD_U_G1_pre2.jar", + "e6583246a85dba26a478dd4925c298bb5b3f277a": "preview_OptiFine_1.15.2_HD_U_G1_pre20.jar", + "84c5d50e26aaa02733fa15a8c13ab845fbdea97f": "preview_OptiFine_1.15.2_HD_U_G1_pre21.jar", + "c55e449386eeca34df5b428d85927242fc841438": "preview_OptiFine_1.15.2_HD_U_G1_pre22.jar", + "459e17c77059671ef5daa9ab794400e8ae25095d": "preview_OptiFine_1.15.2_HD_U_G1_pre23.jar", + "2c6e4f61b1ceea92b76e0be40bc672e5b6d58efe": "preview_OptiFine_1.15.2_HD_U_G1_pre24.jar", + "4ea53e3d202239c5ffa2affa809e36998194f1bc": "preview_OptiFine_1.15.2_HD_U_G1_pre26.jar", + "29861ba50021b08200ef33f77c776f9f5b7135f7": "preview_OptiFine_1.15.2_HD_U_G1_pre27.jar", + "454dc0e03dab9d30a2ccce5823b7a0b309f5c859": "preview_OptiFine_1.15.2_HD_U_G1_pre28.jar", + "3c3623d75dc47722149edfb93fdca121c32e8987": "preview_OptiFine_1.15.2_HD_U_G1_pre29.jar", + "942e23fa0169c693fb1b645fae4db9217c6b5916": "preview_OptiFine_1.15.2_HD_U_G1_pre3.jar", + "bdbd92e5252c0afc0f22d6d5840f96762e5d01ea": "preview_OptiFine_1.15.2_HD_U_G1_pre30.jar", + "5daa9461c5762bdd647665f49d2e0ef0eb348910": "preview_OptiFine_1.15.2_HD_U_G1_pre4.jar", + "0029ab50050d19f4009f0a7d708982dde736f022": "preview_OptiFine_1.15.2_HD_U_G1_pre5.jar", + "646b279927cab6036e8f295efd5bda268acd85cf": "preview_OptiFine_1.15.2_HD_U_G1_pre7.jar", + "f11e79a7bc91a5fd985cb756445fc88e8c2dd31b": "preview_OptiFine_1.15.2_HD_U_G1_pre9.jar", + "1e9869b63a89a8ab81a58e1473dd2c2291fbd943": "preview_OptiFine_1.15.2_HD_U_G6_pre5.jar", + "87533b8ac0dfeb11241041f197f8f95c0ffa8c08": "OptiFine_1.16.1_HD_U_G2.jar", + "3e610c9bd89ae2a54cc06de6dfb1b00cd45e9895": "preview_OptiFine_1.16.1_HD_U_G2_pre1.jar", + "df817e1ab0e577be1dfb6c20a9b628d3d725c780": "preview_OptiFine_1.16.1_HD_U_G2_pre10.jar", + "dbca331e7abbe64d6aa360e6a93beec2935d24e3": "preview_OptiFine_1.16.1_HD_U_G2_pre11.jar", + "6fef880338cabe32443557762ca43fb36fe01f7c": "preview_OptiFine_1.16.1_HD_U_G2_pre12.jar", + "ea7bea6511f3b20aa672544d28dbc6d1a2ee8b41": "preview_OptiFine_1.16.1_HD_U_G2_pre13.jar", + "efdc788f59e1c1bcde3b64430322d8990ce7c7d6": "preview_OptiFine_1.16.1_HD_U_G2_pre14.jar", + "388100110b4dc8ef28eaa2f3a4ae2f33a8beb5ae": "preview_OptiFine_1.16.1_HD_U_G2_pre15.jar", + "63f5f097ce2e678d69767981b09b133ab8eac4f5": "preview_OptiFine_1.16.1_HD_U_G2_pre16.jar", + "c56031c3f6e5dcad536115e9f6a1fb5710b72391": "preview_OptiFine_1.16.1_HD_U_G2_pre2.jar", + "da269cdb109b6441ebed179a8d9f974590006cbb": "preview_OptiFine_1.16.1_HD_U_G2_pre3.jar", + "ad9b5d4e0bdd5697d4f6a6b9c30a64977bb8834e": "preview_OptiFine_1.16.1_HD_U_G2_pre4.jar", + "f6d5ea6cedff1f80bed298f9b456de2448b1f268": "preview_OptiFine_1.16.1_HD_U_G2_pre5.jar", + "f57a4269d404d4025de4d864b88510504e785e19": "preview_OptiFine_1.16.1_HD_U_G2_pre6.jar", + "28b46592e2f510a246a8bf62a2b12e808094167c": "preview_OptiFine_1.16.1_HD_U_G2_pre7.jar", + "26207daec6acc66250116e4f73295c9a031b491a": "preview_OptiFine_1.16.1_HD_U_G2_pre8.jar", + "6902e902866b765ff8e31716980c2067d491e004": "preview_OptiFine_1.16.1_HD_U_G2_pre9.jar", + "dde4274c696063496323731ad7b58e0f6d7265af": "OptiFine_1.16.2_HD_U_G3.jar", + "5b5627a814e8c95aa7ac27fa4cf2cc0b8bd4519b": "OptiFine_1.16.2_HD_U_G5.jar", + "5dafc10061020c519ba4abe4982a7dc80eae30f6": "preview_OptiFine_1.16.2_HD_U_G3_pre1.jar", + "0c0d9828984da34f9d5967027c244bb2c83a4396": "preview_OptiFine_1.16.2_HD_U_G3_pre2.jar", + "7c791e4348808132b8c7b59e6350ae2ee25a1918": "preview_OptiFine_1.16.2_HD_U_G3_pre3.jar", + "9a30a2cbc81095d38b4dc17e7d30a4b84e10e235": "preview_OptiFine_1.16.2_HD_U_G3_pre4.jar", + "ec1a4ce97e29c9ee2ac259d98e35bc386035a66d": "preview_OptiFine_1.16.2_HD_U_G3_pre5.jar", + "e03b0cdfbbcf01b1371b3e1090dee64e60a759d8": "preview_OptiFine_1.16.2_HD_U_G3_pre6.jar", + "40ae5fbb4e3724d6e5525d2688445c09ecfe5d39": "preview_OptiFine_1.16.2_HD_U_G3_pre7.jar", + "dc83d55f74722e1e6f503817e00e5ba2e6cb4abb": "OptiFine_1.16.3_HD_U_G3.jar", + "89d9472ee507946caa227e226524e5260ed944db": "OptiFine_1.16.3_HD_U_G4.jar", + "6a8f57ca84628da47b011e608ff449cde7ec13ed": "OptiFine_1.16.3_HD_U_G5.jar", + "f53bad69adaac9e523577dcb1c65b158802f3dee": "preview_OptiFine_1.16.3_HD_U_G3_pre2.jar", + "5b970eeae2ba07316b3e3a53da1d71de87c9f9f2": "preview_OptiFine_1.16.3_HD_U_G4_pre1.jar", + "599d9de6b656077b03fbebc9478924c548a6e9e0": "OptiFine_1.16.4_HD_U_G5.jar", + "7cce72db1f77c6178b549fa430e241eadb318f69": "OptiFine_1.16.4_HD_U_G6.jar", + "f49c042fa099907eafcbe22d7035d84f8b5715f1": "OptiFine_1.16.4_HD_U_G7.jar", + "f0b1eb59acdae57e10bb928046ee26012ef220ad": "preview_OptiFine_1.16.4_HD_U_G6_pre12.jar", + "ded7894eebda787b7bd5013aa8c66577f2ba03fc": "preview_OptiFine_1.16.4_HD_U_G7_pre1.jar", + "57cfcec3dd99b9f0950d0d6305bc95ecd8563212": "OptiFine_1.16.5_HD_U_G6.jar", + "706fb33784045d69e7ac5730426d813a39511dcd": "OptiFine_1.16.5_HD_U_G7.jar", + "137363842c5bfdebb72356a4079faeef3157eb3c": "OptiFine_1.16.5_HD_U_G8.jar", + "ad0f1577ce02a703185e46ee9e24fdeb65479fd5": "preview_OptiFine_1.16.5_HD_U_G7_pre10.jar", + "11770876367980da546c68bc95c4f794582907f2": "preview_OptiFine_1.16.5_HD_U_G7_pre11.jar", + "c5b32d2bad3bd54c31ddb29869a55fe44409cfe2": "preview_OptiFine_1.16.5_HD_U_G7_pre2.jar", + "9fc9cc09e72a3060d577f62f3535820056b0c2d4": "preview_OptiFine_1.16.5_HD_U_G7_pre4.jar", + "577fae32b60ac8d1b91e3fa645dc004d4a9e513e": "preview_OptiFine_1.16.5_HD_U_G7_pre5.jar", + "4bccf7f371ac83523796b4706569cc5077c46aea": "preview_OptiFine_1.16.5_HD_U_G7_pre6.jar", + "97b8d413a909354e2e0f42943cf292941dfe4c38": "preview_OptiFine_1.16.5_HD_U_G7_pre7.jar", + "9c48a804ee990e526000c7061bde9aa6f75d62a6": "preview_OptiFine_1.16.5_HD_U_G7_pre8.jar", + "9a9b69a52a4514f5d25c9f47127a822e93458fa3": "preview_OptiFine_1.16.5_HD_U_G7_pre9.jar", + "d16a450ec4205d02319480799008d8177ba05d69": "preview_OptiFine_1.16.5_HD_U_G8_pre1.jar", + "539c215aa0e8b8f696b325e89f3f9a3658ab32c8": "preview_OptiFine_1.16.5_HD_U_G8_pre10.jar", + "311ed81a27d24047b6ade33f46ac21c962897016": "preview_OptiFine_1.16.5_HD_U_G8_pre11.jar", + "4bc50f85809a299d30c3e3e73cfd87cad61cf7e6": "preview_OptiFine_1.16.5_HD_U_G8_pre12.jar", + "7de1b8e9f7cd6d7ca941d90121e50206aba80c68": "preview_OptiFine_1.16.5_HD_U_G8_pre13.jar", + "a4e021a75aab7ed1eb83ff7b45182d497aa930f2": "preview_OptiFine_1.16.5_HD_U_G8_pre14.jar", + "0ddcc4f47e10b2d671ebbf9147eb16cf8f1bb92a": "preview_OptiFine_1.16.5_HD_U_G8_pre2.jar", + "b8600be4fde6f8cc3c28173cc9bde6923956ff42": "preview_OptiFine_1.16.5_HD_U_G8_pre3.jar", + "d46b10098309929d9a6583323f059e65ba40a5e5": "preview_OptiFine_1.16.5_HD_U_G8_pre4.jar", + "e835424b36fdae3d5f46c6bf096852848127f1e7": "preview_OptiFine_1.16.5_HD_U_G8_pre5.jar", + "5a84983221331597f58e9636d41f1fb0c9e3de61": "preview_OptiFine_1.16.5_HD_U_G8_pre6.jar", + "d03ce2209d787539e5e51f80a7cbffc69a49ce9f": "preview_OptiFine_1.16.5_HD_U_G8_pre7.jar", + "1c646dc058aa32898917589858d900407883ba3f": "preview_OptiFine_1.16.5_HD_U_G8_pre8.jar", + "9bf369aa609e5612247473326dc298e6e6c9fecf": "preview_OptiFine_1.16.5_HD_U_G8_pre9.jar", + "b88b05278fbbf400cb5ec0cc26e0c5b7306e0c1e": "preview_OptiFine_1.17_HD_U_G9_pre16.jar", + "a6beac3091d17a4ba420116c75c89faeaee48539": "preview_OptiFine_1.17_HD_U_G9_pre17.jar", + "2839c5e74f64500561ee3e8605c51d9f06727c22": "preview_OptiFine_1.17_HD_U_G9_pre18.jar", + "455243ff55177189fff140ba7f4c4777cc977ef6": "preview_OptiFine_1.17_HD_U_G9_pre19.jar", + "bcc390e34bd529a86e6ae0e25d50417f46ba482f": "preview_OptiFine_1.17_HD_U_G9_pre20.jar", + "d30c23d745df54fd6ea2374e9c0b4392f0a78199": "preview_OptiFine_1.17_HD_U_G9_pre21.jar", + "075e7f8473b6785c3c6bbeeb41a3728f6ff3c3ba": "preview_OptiFine_1.17_HD_U_G9_pre22.jar", + "2ffaee36c717ae918cc93661df739740636900c9": "preview_OptiFine_1.17_HD_U_G9_pre23.jar", + "4385493e7ce4c6f4a3945f1d96246bce0b151c7e": "preview_OptiFine_1.17_HD_U_G9_pre24.jar", + "bcc33e67f134861eb30b1f0b9a829183c8afdebf": "preview_OptiFine_1.17_HD_U_G9_pre25.jar", + "6a8e34605cc828091027d9f011b804d916808af4": "preview_OptiFine_1.17_HD_U_G9_pre26.jar", + "4714bdceaefc15e0af9d5c72de8c05cf3051d760": "OptiFine_1.17.1_HD_U_G9.jar", + "cc64084e73d6423baf7ccbdd9f3760dd56f2fce5": "OptiFine_1.17.1_HD_U_H1.jar", + "1ee73bd882385a7abb9896b9469586741a6850a8": "preview_OptiFine_1.17.1_HD_U_G9_pre26.jar", + "537c1fbee9ebe25b49a8e6fb93db162d217f1c83": "preview_OptiFine_1.17.1_HD_U_G9_pre27.jar", + "68e1124f4b0376773d8d581da78f9bb6fc76486e": "preview_OptiFine_1.17.1_HD_U_G9_pre28.jar", + "3f80a562b39439831491c650c65ecb762e70d5f7": "preview_OptiFine_1.17.1_HD_U_G9_pre29.jar", + "9751c69568a61ce5b2a602b4dcf77c1cea57c709": "preview_OptiFine_1.17.1_HD_U_G9_pre30.jar", + "162fa1b4a0d5b89dad6030b5fd5903a3f24d39c7": "preview_OptiFine_1.17.1_HD_U_G9_pre31.jar", + "c4f3a33d892948927cecfaa60b284217325557c5": "preview_OptiFine_1.17.1_HD_U_G9_pre32.jar", + "12dbbc8ccd8dc02806768fb3fbe4a2d8c7c11301": "preview_OptiFine_1.17.1_HD_U_G9_pre33.jar", + "f74d1dfcb24c43e633076c63dd4492b29fca14b3": "preview_OptiFine_1.17.1_HD_U_G9_pre34.jar", + "42e55681fb106475ef13d5c5ef7113fb631392d4": "preview_OptiFine_1.17.1_HD_U_G9_pre35.jar", + "245ddb68349f5f81d067f4d9402d62db3d439d50": "preview_OptiFine_1.17.1_HD_U_H1_pre1.jar", + "0ac033f5ba4089311af55172281e73df143b9ac4": "preview_OptiFine_1.17.1_HD_U_H1_pre2.jar", + "68a3727bcdb0eac0986ff4e883d93fef406eb1c3": "preview_OptiFine_1.17.1_HD_U_H1_pre3.jar", + "a6534877558f0438bda604dc43093d61c6de88ae": "preview_OptiFine_1.17.1_HD_U_H1_pre4.jar", + "3e18db17b8695feb0a188ecdcb81b561d9d7030c": "preview_OptiFine_1.17.1_HD_U_H1_pre5.jar", + "c2e0c4391a671cd06931f7b6e965085c04c58762": "preview_OptiFine_1.17.1_HD_U_H1_pre6.jar", + "1ffb7325df5bf01b1d99bbbed8006a477b861394": "preview_OptiFine_1.17.1_HD_U_H1_pre7.jar", + "f2fa727e67b4795ddea3cc37cc5ced42e9c3b0c5": "preview_OptiFine_1.17.1_HD_U_H1_pre8.jar", + "aee167aff9937a068d514c85e3f8c766db99ed43": "preview_OptiFine_1.17.1_HD_U_H1_pre9.jar", + "59709e09632c80ab80ffcc4b5bf380d073bf467f": "preview_OptiFine_1.17.1_HD_U_H2_pre1.jar", + "52629bc5319ec203edd42de1776cb8d67a0e6b1b": "OptiFine_1.18_HD_U_H3.jar", + "e5077b4be03956118613162b5dbcf90406b27747": "preview_OptiFine_1.18_HD_U_H3_pre7.jar", + "326c160481cc849bccb821939992c101a8fe163b": "OptiFine_1.18.1_HD_U_H4.jar", + "1679654fdf49fd1216b090d51d3d8ec906094619": "OptiFine_1.18.1_HD_U_H5.jar", + "c86534ee821a78efc741c9cb407eec76625e1bab": "OptiFine_1.18.1_HD_U_H6.jar", + "34c98382131efa1394cff06528238dc44d255794": "preview_OptiFine_1.18.1_HD_U_H4_pre1.jar", + "d6cd0a2d0243ecad107220bff639eb46277762ea": "preview_OptiFine_1.18.1_HD_U_H5_pre1.jar", + "e24d766864f9238b94be5b3ad65608d7eb521fad": "preview_OptiFine_1.18.1_HD_U_H5_pre2.jar", + "bc1b87a6379acf5a5dca2a56196080ace49e4864": "preview_OptiFine_1.18.1_HD_U_H5_pre3.jar", + "555a6b8e420bd63a04d9f338d61f3333b4ccf5d9": "preview_OptiFine_1.18.1_HD_U_H5_pre4.jar", + "1d02694393f10992cbbd11a802568b0890b4dc81": "preview_OptiFine_1.18.1_HD_U_H5_pre5.jar", + "e3a22adc6931dcd7513f26afa1adecd62a35641b": "preview_OptiFine_1.18.1_HD_U_H5_pre6.jar", + "445a8cb8a1d565d8a1fabc0ba984c5936edbf561": "OptiFine_1.18.2_HD_U_H6.jar", + "8d83d4d5e325887e000a462029d2bf7a6e77eebc": "OptiFine_1.18.2_HD_U_H7.jar", + "2a461188167c1da6b1cfd520d85a656f45c74d70": "OptiFine_1.18.2_HD_U_H9.jar", + "e591777f272df533b62260a6dbe0e6cde62066f6": "preview_OptiFine_1.18.2_HD_U_H6_pre1.jar", + "f7c727c99e7f58ef9b86cc96fb894f628391016c": "preview_OptiFine_1.18.2_HD_U_H7_pre1.jar", + "cb51529f94e0eff5b9da92c8f70b1d498662a115": "preview_OptiFine_1.18.2_HD_U_H7_pre2.jar", + "ceea3c6612d2ec36c6ef1ca4a9355a141df9089f": "preview_OptiFine_1.18.2_HD_U_H7_pre3.jar", + "596c3a399889c3e2ef1d8fee994e8cf5a91e457e": "preview_OptiFine_1.18.2_HD_U_H8_pre1.jar", + "85f545aeb9d232ae970b0efa24e833f9c5d0fe33": "preview_OptiFine_1.18.2_HD_U_H9_pre1.jar", + "b9bbabb8f31f2b04ca1a1a8645df1be701f705bd": "preview_OptiFine_1.18.2_HD_U_H9_pre2.jar", + "c2bf213b4f60b6a29e402e3eaf219a4c85a5d1e6": "preview_OptiFine_1.18.2_HD_U_H9_pre3.jar", + "64c8c35313d25a64a8a8677cb851a2c83556ddc8": "OptiFine_1.19_HD_U_H8.jar", + "c668eaf9b6ce4e64f26a29fb27e830229ab4bef3": "OptiFine_1.19_HD_U_H9.jar", + "cda9130e0d36f0d6e06950ab393a8929b502a440": "preview_OptiFine_1.19_HD_U_H8_pre1.jar", + "cd1ae81f544672df776dc4426269771d2e82d0b2": "preview_OptiFine_1.19_HD_U_H8_pre10.jar", + "cb3ad2a9b4c23675ca569058f55dd174eed67a38": "preview_OptiFine_1.19_HD_U_H8_pre2.jar", + "2f7139ea0888ed18006e3a39b7475e88a3fd545d": "preview_OptiFine_1.19_HD_U_H8_pre3.jar", + "85cd70bb714e65935abf351811889a47a648bb96": "preview_OptiFine_1.19_HD_U_H8_pre4.jar", + "c65cbae4c1cfb5b9a5a573e4b83424df2d81b172": "preview_OptiFine_1.19_HD_U_H8_pre5.jar", + "10cd57890ebf449f5c258cff1c60d8dcefcc6cf6": "preview_OptiFine_1.19_HD_U_H8_pre6.jar", + "eb03e60c2fa9904a47eec1ef4a2c8804ee6cc0b8": "preview_OptiFine_1.19_HD_U_H8_pre7.jar", + "40d0b514168161fdefc91594f0d3f774f831d7de": "preview_OptiFine_1.19_HD_U_H8_pre8.jar", + "8e5bedd1c1a3641caad0743c4629db51c11f8feb": "preview_OptiFine_1.19_HD_U_H9_pre1.jar", + "7745216a810f0d617ed17a520fda44e12456492f": "preview_OptiFine_1.19_HD_U_H9_pre2.jar", + "6d6ed920eb99836e76549a7803af85881fcdee34": "preview_OptiFine_1.19_HD_U_H9_pre3.jar", + "7604e63ca74876b4e8ea9c3a36dbe80111c776b1": "preview_OptiFine_1.19_HD_U_H9_pre4.jar", + "b42dcce86c88ad4bcfa52a9d297afa800477aa78": "OptiFine_1.19.1_HD_U_H9.jar", + "648427a02940b3c53b3678cd5df1ff6997bd1e6c": "preview_OptiFine_1.19.1_HD_U_H9_pre1.jar", + "7cc221a6df6831b62c3b676b5f958e82a4487fd8": "OptiFine_1.19.2_HD_U_H9.jar", + "f304c56c7ab9119ce4269f2b019cd4668f48376d": "OptiFine_1.19.2_HD_U_I1.jar", + "708c1ecc148a0bd7d341e8f5e005e670903650f7": "OptiFine_1.19.2_HD_U_I2.jar", + "e0e924990bd12cc499fc89439f0b2c50b6e147fa": "preview_OptiFine_1.19.2_HD_U_I1_pre1.jar", + "465b229481c0a285195a168bf6581549a5cd7373": "preview_OptiFine_1.19.2_HD_U_I1_pre2.jar", + "af4398d0ce0628bcc0204e61c157e3c8951b9ad5": "preview_OptiFine_1.19.2_HD_U_I1_pre3.jar", + "35ef18f953bbea9a4cba6e14d0731d56f888114f": "preview_OptiFine_1.19.2_HD_U_I1_pre4.jar", + "b24d8725a8d06e1c190d1d2b3bc51c899f74b36d": "OptiFine_1.19.3_HD_U_I3.jar", + "f5ce615d9886e377079fd13cc4700a51d5a9c7b6": "preview_OptiFine_1.19.3_HD_U_I2_pre1.jar", + "10e6d97ea2c97cf0575a7c587fb11e1ddb73aa71": "preview_OptiFine_1.19.3_HD_U_I2_pre2.jar", + "89718cd608225d7c23f7e180d011daa4a47ce6bd": "preview_OptiFine_1.19.3_HD_U_I2_pre3.jar", + "204baf0fd59432054aebd2f83c83fe15fdd3e611": "preview_OptiFine_1.19.3_HD_U_I2_pre4.jar", + "90a52b3adc94a1b9a70f03f4c4ea14dfdf3424d8": "preview_OptiFine_1.19.3_HD_U_I2_pre5.jar", + "050d787936cc239ea8db0aa250d2ed72a44fa71d": "OptiFine_1.19.4_HD_U_I3.jar", + "253e9d29c6826c134fbdeea074d1d91c3410225c": "OptiFine_1.19.4_HD_U_I4.jar", + "068b998d6c5db5b3bb7ad6a0a7d0c6aefc4dc191": "preview_OptiFine_1.19.4_HD_U_I3_pre3.jar", + "a8b9156811c4745dbd612ebbb1df6f93d2fa734a": "preview_OptiFine_1.19.4_HD_U_I3_pre4.jar", + "6f2793e6236a27c06a74e8a1876ea6b29be038a7": "preview_OptiFine_1.19.4_HD_U_I3_pre5.jar", + "9ad82ef9f32fb5a0e7e7e0c7e8906c9f319a9705": "preview_OptiFine_1.19.4_HD_U_I3_pre6.jar", + "cf02ee8b5cf6922fbc28d6ffc8183949fff55d49": "preview_OptiFine_1.19.4_HD_U_I3_pre7.jar", + "702e0e9987209f66a5b4f825e7ac658fd49dd4b1": "preview_OptiFine_1.19.4_HD_U_I3_pre8.jar", + "04c8eaeadf24c42929500812a8f3f5fe634f0d1f": "preview_OptiFine_1.19.4_HD_U_I3_pre9.jar", + "968784e051c2c44f10a4ab448c979572977969c0": "preview_OptiFine_1.19.4_HD_U_I4_pre1.jar", + "abe4b1dbda7d95eb47953ef8aa11ed8ed1d8e238": "preview_OptiFine_1.20_HD_U_I5_pre3.jar", + "6612ce97d846018126f91cdbae23f0eff6bd2bab": "preview_OptiFine_1.20_HD_U_I5_pre4.jar", + "f37d62b6a071f6cd3d6bdfa378b56f7f5b9ec7c6": "preview_OptiFine_1.20_HD_U_I5_pre5.jar", + "c44bd276edd10fa910d929a4e7f6b8c7ffe6c23f": "OptiFine_1.20.1_HD_U_I5.jar", + "227914ab1037762eae44a2e66a568b87d7a02365": "OptiFine_1.20.1_HD_U_I6.jar", + "7484cf60bd8e019c40fa448daa294bd31f52962b": "preview_OptiFine_1.20.1_HD_U_I5_pre4.jar", + "953b1b4f15f56329bc4ade851846ef004ebbd5d0": "preview_OptiFine_1.20.1_HD_U_I5_pre5.jar", + "ebbdb3076f8291d7f8e10cbec3b1d3c49a0a701d": "preview_OptiFine_1.20.1_HD_U_I5_pre6.jar", + "b831d59eeb63122893f87a62ec3e5b3f1cc5586a": "preview_OptiFine_1.20.1_HD_U_I5_pre7.jar", + "dc93d991b07b2c5c6d2cd50a553599900c256264": "preview_OptiFine_1.20.1_HD_U_I5_pre8.jar", + "040a73427541170bccf162c04f4129b3909b8da1": "preview_OptiFine_1.20.1_HD_U_I5_pre9.jar", + "9f5fd75ede7770ad35383443b0a203f026cef5db": "preview_OptiFine_1.20.1_HD_U_I6_pre1.jar", + "85d5566b440036b926c5dd306108e48e7fb9b6e8": "preview_OptiFine_1.20.1_HD_U_I6_pre2.jar", + "d09c110358117336472414720bf961a581690dc5": "preview_OptiFine_1.20.1_HD_U_I6_pre3.jar", + "3365577f5161f9a1bbfefb15675bb5ece27600e4": "preview_OptiFine_1.20.1_HD_U_I6_pre4.jar", + "f317c701ae8c533fc34809dbce914bcced4e9a03": "preview_OptiFine_1.20.1_HD_U_I6_pre5.jar", + "445b32b14a2c2802d5a0832a708712d2fabafbc2": "preview_OptiFine_1.20.1_HD_U_I6_pre6.jar", + "9ea6b1389e88b81797f54b9d8d1e870d3fe8532c": "preview_OptiFine_1.20.2_HD_U_I7_pre1.jar", + "f2475e8d5746477d1d2972cd5aae62a5d9c04264": "OptiFine_1.20.4_HD_U_I7.jar", + "fec28a2fa152744bc2b47b3c5db3217974aa57ee": "preview_OptiFine_1.20.4_HD_U_I7_pre2.jar", + "4068365a57f4b154f4ebbc670894dc9038cf3819": "preview_OptiFine_1.20.4_HD_U_I7_pre3.jar", + "2481e0bed7610bfdfe26dea5a6bd8f01537c4ea8": "preview_OptiFine_1.20.4_HD_U_I7_pre4.jar", + "150990ba068cfb55bbf9e525c5ab84db0b8903e1": "preview_OptiFine_1.20.4_HD_U_I7_pre5.jar", + "fcf0f0f278d1901e4341d5ec8022bede03572430": "preview_OptiFine_1.20.4_HD_U_I7_pre6.jar", + "1c3395efa928758505b808291162a2514523b399": "preview_OptiFine_1.20.4_HD_U_I7_pre7.jar", + "4bcfec24e5d917ee96e0de6ef095bfc08782217b": "preview_OptiFine_1.20.4_HD_U_I8_pre1.jar", + "c931df21309800f1d1ece59b318e6b3e39c98176": "preview_OptiFine_1.20.4_HD_U_I8_pre2.jar", + "83f5ad147536405d86bf4ee47a7f7970e1fa8329": "preview_OptiFine_1.20.4_HD_U_I8_pre3.jar", + "7c6669319db18c9622d896da2a061479be88acf5": "preview_OptiFine_1.20.4_HD_U_I8_pre4.jar", + "bda7bbdd5e2d8c73148372e8c3b34d7c2485a4e8": "preview_OptiFine_1.20.6_HD_U_J1_pre18.jar", + "24a20f949a719480523669669a4e5e7339df6186": "OptiFine_1.21.1_HD_U_J1.jar", + "6bc74af8f733864539404a3f087aeb6f0cadc6b1": "preview_OptiFine_1.21.1_HD_U_J1_pre10.jar", + "4813a6cde6b0f5b20223d486ba7adf0816236a77": "preview_OptiFine_1.21.1_HD_U_J1_pre11.jar", + "c99ea98d8537f99b7470f1bcfa81c7ce405fd97e": "preview_OptiFine_1.21.1_HD_U_J1_pre12.jar", + "f2f55e6d61a4b90bdaa31eb520f5af78f8a1833b": "preview_OptiFine_1.21.1_HD_U_J1_pre13.jar", + "53a15b75ea9d20b644b0d3e9c7280ba172ade053": "preview_OptiFine_1.21.1_HD_U_J1_pre14.jar", + "da5575e1138fc2a4c28779cfc5e7d260253ccbdb": "preview_OptiFine_1.21.1_HD_U_J1_pre15.jar", + "0930876341beadf7014f57e9ebdd44be445dacbe": "OptiFine_1.21.3_HD_U_J2.jar", + "350f63ec3e59d21227f803d4078883884e482660": "preview_OptiFine_1.21.3_HD_U_J2_pre10.jar", + "2c48a4210acd25082c220f9601f17d1cc7769098": "preview_OptiFine_1.21.3_HD_U_J2_pre11.jar", + "8f0a097b1f7c9d065396bac9230eab62412a8b54": "preview_OptiFine_1.21.3_HD_U_J2_pre12.jar", + "f97df5638fcda7f4380d9284b329569d4e1554aa": "preview_OptiFine_1.21.3_HD_U_J2_pre8.jar", + "77df50552deea51687592b60c6d24561ee11aa74": "preview_OptiFine_1.21.3_HD_U_J2_pre9.jar", + "bcc6a7a140320566a985b1499a59fcb085255d14": "OptiFine_1.21.4_HD_U_J3.jar", + "e7bff4575b7311e11d7111506e855b4ad910481f": "preview_OptiFine_1.21.4_HD_U_J3_pre10.jar", + "52b2b6defdb8d1c189afb42125079f1b76ff72e2": "preview_OptiFine_1.21.4_HD_U_J3_pre11.jar", + "708f70aa5f70919a40d02ba75fc30d2d60c8e7cd": "preview_OptiFine_1.21.4_HD_U_J3_pre12.jar", + "86b796325d03d4571809a155c54cf6cba7c7482b": "preview_OptiFine_1.21.4_HD_U_J3_pre13.jar", + "618719a6a69eb674ea50c82cffe3d4e3c2f67abf": "preview_OptiFine_1.21.4_HD_U_J3_pre14.jar", + "98dd84b0581fcf7a27aa96238acb615b5b719825": "preview_OptiFine_1.21.4_HD_U_J3_pre15.jar", + "5c8573ae921082a069a9e7269500178f0c580c97": "preview_OptiFine_1.21.4_HD_U_J3_pre4.jar", + "17d51e1bfc94dde95368ab91aa0bb56e89c706bf": "preview_OptiFine_1.21.4_HD_U_J3_pre5.jar", + "1a4311606ac94d682800714793ea887c3276b18d": "preview_OptiFine_1.21.4_HD_U_J3_pre6.jar", + "12bf1a1b5607b5c62405a8c6ab4e1bd02bef2c8d": "preview_OptiFine_1.21.4_HD_U_J3_pre7.jar", + "f1f56403dc575fd975a000bfb133060285cacd6f": "preview_OptiFine_1.21.4_HD_U_J3_pre8.jar", + "9584de0e8204f5c855b60741d58af967dfc599a8": "preview_OptiFine_1.21.4_HD_U_J3_pre9.jar", + "0b97631e166ace9346744de33b7772df7bf5fbeb": "preview_OptiFine_1.21.4_HD_U_J4_pre1.jar", + "a776ec6c072529d4b731e0e6929a8aee56d04e76": "preview_OptiFine_1.21.4_HD_U_J4_pre2.jar", + "2a3fdfcbfc9c572b22c7d15c6129b8ea58ea1712": "preview_OptiFine_1.21.8_HD_U_J6_pre13.jar", + "ba7ca56c9b33d2fc7f7d22e20ce2765e4d257299": "preview_OptiFine_1.21.8_HD_U_J6_pre14.jar", + "097d3acd315b0682a4565ab9cbb4cdc1cc17d81f": "preview_OptiFine_1.21.8_HD_U_J6_pre15.jar", + "d7effca0de34c9442f06776848679a684f1f166a": "preview_OptiFine_1.21.8_HD_U_J6_pre16.jar", + "696ed52cdfd0ac2eafab8e98575d146b400de530": "preview_OptiFine_1.21.10_HD_U_J7_pre10.jar", + "be7bc81f0c9ce15316f76a059b0a690f62c03a5d": "preview_OptiFine_1.21.10_HD_U_J7_pre11.jar", + "731e698abe94bbd90cda628f80b4ce6ac5af8188": "preview_OptiFine_1.21.10_HD_U_J7_pre6.jar", + "63f189a4b7ff5b53e5bc56391f5274eab8555def": "preview_OptiFine_1.21.10_HD_U_J7_pre7.jar", + "7ccfc0085ccdef3f4ec21918a059ddd05171711a": "preview_OptiFine_1.21.10_HD_U_J7_pre8.jar", + "aad53ad3346944a12ed088ffbba98cf9eccc474c": "preview_OptiFine_1.21.10_HD_U_J7_pre9.jar", + "2800efeb56565f23494d372e7edac29333113e11": "OptiFine_1.21.11_HD_U_J8.jar", + "cee35b83c75da33dc07ffbb344386cae5cdc5c5b": "OptiFine_1.21.11_HD_U_J9.jar", + "687046282c370e690ec4b4aa6d90a6b42ada3ee1": "preview_OptiFine_1.21.11_HD_U_J8_pre10.jar", + "c09b05637610072bc33023d1c901e2726b7d16ce": "preview_OptiFine_1.21.11_HD_U_J8_pre11.jar", + "88682b40f08f462c4daae8e6b25ea99b1ed5cd14": "preview_OptiFine_1.21.11_HD_U_J8_pre12.jar", + "1c69de725c249d487df676470c71a7c05ece943c": "preview_OptiFine_1.21.11_HD_U_J8_pre3.jar", + "921b68e014539d75b77a47ca2117fc9e09ace4ed": "preview_OptiFine_1.21.11_HD_U_J8_pre4.jar", + "2fee496a35eeebf39fbb43017b12d752f5c37dee": "preview_OptiFine_1.21.11_HD_U_J8_pre5.jar", + "e5b5da810d43c02dccedfd99f83ae8ad63e49966": "preview_OptiFine_1.21.11_HD_U_J8_pre6.jar", + "8309638bab4d99258354b4d13c3cd82ff7c9645b": "preview_OptiFine_1.21.11_HD_U_J8_pre7.jar", + "b3d208ec7b5015842fcc77b3399b04b24e0b54cb": "preview_OptiFine_1.21.11_HD_U_J8_pre8.jar", + "c4ac7401bb5f668e4e92121501d1537cdbe7343c": "preview_OptiFine_1.21.11_HD_U_J8_pre9.jar", + "fd96031b5fc4c41ad71581eea53eb122022fff5b": "preview_OptiFine_1.21.11_HD_U_J9_pre1.jar", + "a159a217aba02cd7897394a51bd346c3b658b9f9": "preview_OptiFine_1.21.11_HD_U_J9_pre2.jar", + "2a784437076b539ea1a3c1a37ec98ff21bc3952c": "preview_OptiFine_1.21.11_HD_U_J9_pre3.jar", + "ee32ce4b406e290a0e99685afd09dc44cad03ddd": "preview_OptiFine_1.21.11_HD_U_J9_pre4.jar" +} diff --git a/PCLCS/PCLCS.csproj b/PCLCS/PCLCS.csproj index ad6fb5f..f4a3203 100644 --- a/PCLCS/PCLCS.csproj +++ b/PCLCS/PCLCS.csproj @@ -27,6 +27,7 @@ + @@ -58,6 +59,7 @@ + diff --git a/PCLCS/Resource/WikiEntries.txt b/PCLCS/Resource/WikiEntries.txt index af27856..8adca9b 100644 --- a/PCLCS/Resource/WikiEntries.txt +++ b/PCLCS/Resource/WikiEntries.txt @@ -3,7 +3,7 @@ industrial-craft@|工业时代2 (Industrial Craft 2) @redpower2-core|红石力量2 (RedPower2) buildcraft@|建筑 (BuildCraft) forestry@|林业* -railcraft@railcraft-reborn|铁路*¨railcraft-reborn|铁路 (Railcraft) +railcraft|铁路* @ee2|等价交换2 (Equivalent Exchange 2) @@ -16,7 +16,7 @@ powerconverters-portablejims-fork|能源转换 (PowerConverters)¨powerconverter modular-forcefield-system|模块化力场系统* -minechem-recompile|化学 (MineChem) - 非官方重编译版¨minechem-archive|化学 (MineChem)¨73373|化学 (MineChem) +minechem|化学 (MineChem)¨minechem-recompile|化学 (MineChem) - 非官方重编译版¨minechem-archive|化学 (MineChem) iron-chests@|更多箱子*¨iron-chests-for-fabric@cyberanner-ironchest|更多箱子 (Iron Chests)¨iron-chests-fabric@|更多箱子 (Iron Chests) balkons-weaponmod|更多武器 (Balkon's WeaponMod) advanced-machines|高级机械 (AdvancedMachines) - Chocohead版¨@advanced-machines-uuum|高级机械 (AdvancedMachines) - Immibis版 @@ -192,7 +192,7 @@ invasions|入侵 (Invasion) mb-battlegear-2|挖矿与砍杀:双持与战备 2 (Mine & Blade: Battlegear 2) galacticraft-legacy@|星系 (GalactiCraft) - +classic-dungeon-pack-mod|地牢拓展 (DungeonPack) journeymap@|旅行地图 (JourneyMap) @@ -205,7 +205,7 @@ ars-magica-2|魔法艺术2* highlands|高地* doggy-talents|小狗天才* plasmacraft|电浆工艺 (PlasmaCraft) - +more-health-enhanced|更多生命 (More Health ENHANCED) fossils@fossils-and-archeology-revival|考古与化石 (Fossils and Archeology Revival) glenns-gases|格林的气体 (Glenn's Gases) the-lord-of-the-rings-mod-legacy|魔戒:传承 (The Lord of the Rings Mod: Legacy) @@ -232,7 +232,7 @@ animal-bikes|动物坐骑* electro-magic-tools|电力魔法工具 (Electro-Magic Tools) forbidden-magic|禁忌魔法* mobtalker2|怪物交谈2 (MobTalker 2) -icbm-classic@icbm|洲际导弹 (ICBM)¨icbm|洲际导弹 (ICBM) - 已停更 +icbm-2|洲际导弹 (ICBM) - 已停更 thaumic-infusion|神秘注魔* thaumic-exploration|神秘探险* deconstruction-table|解构工作台* @@ -278,7 +278,7 @@ betterfarming|农业增强 (Better Farming) lots-of-food|更多食物 (Lots of Food) -aquaculture@|水产业2/水产品2 (Aquaculture 2) +aquaculture@|水产业2 / 水产品2 (Aquaculture 2) project-bench|高级工作台* apples-plus|更多苹果 (Apples+) @@ -323,7 +323,7 @@ metallurgy|冶金* school-mod-forge-1-14|学校 (School Mod) witchery|巫术* -growthcraft|生长工艺 (GrowthCraft)¨growthcraft-community-edition|生长工艺 (GrowthCraft) +growthcraft-community-edition|生长工艺 (GrowthCraft / Growthcraft Community Edition)¨growthcraft|生长工艺 (GrowthCraft / Growthcraft Community Edition) gregtech-5-unofficial|格雷科技5 (GregTech 5) mantle@|地幔* @@ -399,7 +399,7 @@ atomicstrykers-magic-yarn|AtomicStryker的魔法线 (AtomicStryker's Magic Yarn) teds-old-world-gen-mod|怀旧地形 (Old World Gen) -academycraft|超能力/学园都市 (AcademyCraft) +academycraft@|超能力/学园都市 (AcademyCraft)¨academycraft-official|超能力/学园都市 (AcademyCraft) - 接手后的 the-spice-of-life|生活调味料 (The Spice of Life) @@ -697,7 +697,7 @@ clockwork-phase|时之沙* autoreglib@ roots|根源魔法* -guide-api|指南 (Guide-Api)¨@guide-api-legacy|指南 (Guide-Api) - Archive +guide-api|指南 (Guide-API)¨@guide-api-legacy|指南 (Guide-API) - Archived fraghappy-reborn genetics-reborn|基因重生* @@ -709,7 +709,7 @@ taoism|御灵* essentialcraft-4-unofficial|源质魔法4非官方版 (EssentialCraft 4 Unofficial) celestial-craft|天体* lost-thaumaturgy|失落神秘 (Pengu's Lost Thaumaturgy) -compact-machines|更多空间/压缩空间* +compact-machines|更多空间 / 压缩空间* ex-nihilo-omnia|无中生有-世间万物* last-sword-you-will-ever-need-mo@the-last-sword-you-will-ever-need-mod|最终之剑 (The Last Sword You Will Ever Need Mod) @@ -719,12 +719,12 @@ ftb-library-legacy-forge|FTB Library(旧版) (FTB Library (Forge) (Legacy)) thaumic-potatoes-2|神秘土豆2* -extraplanets|额外行星 (Extra Planets) +extraplanets@|额外行星 (Extra Planets) xtracraft-mod|X工艺 (XtraCraft Mod) formivore-generators-walled-city-great-wall-ca|城墙城市生成 (Cellular Automata Generator/Great Wall Mod/Walled City Generator) - +icbm@|洲际导弹经典版 (ICBM - Classic) voltz-engine blocklings|方块酱* @@ -923,7 +923,7 @@ better-hud|更好的HUD (Better HUD) custom-stuff-4|自定义物品4* powered-thingies|特斯拉科技 (Tesla Powered Thingies) -mcmusicplayer#t1:description +mcmusicplayer obsidian-suite wizards-of-lua|Lua魔导师 (Wizards of Lua) mystical-agriculture@|神秘农业* @@ -1113,7 +1113,7 @@ avaritiaddons|无尽收容 / 更多空间箱子* base immersive-cables|沉浸线缆* -better-title-screen|更好的标题页* +better-title-screen@bettertitlescreen|更好的标题页* sound-filters|声音滤波器* lambda-craft|半条命 (LambdaCraft) vivecraft@¨vivecraft-forge-extensions @@ -1161,7 +1161,7 @@ jaopcaadditions|JAOPCA附属 (JAOPCAAdditions) cloche-call mouse-tweaks|鼠标手势*¨@mouse-tweaks|鼠标手势 (Mouse Tweaks) - 仅部分 jargca|JAOPCA神秘农业附属 (Just A Resource Generation Compatibility Attempt) -sky-resources|空岛资源2 (Sky Resources2) +sky-resources|空岛资源2 (Sky Resources 2) hexxit-gear|Hexxit 装备*¨hexxit-gear-restored|Hexxit 装备 (Hexxit Gear) magic-clover|幸运四叶草* @@ -1264,7 +1264,7 @@ crop-eating-animals|动物自动喂食 (Crop-Eating Animals) refraction|折射* librarianlib@|图书馆 (LibrarianLib) -creeper-confetti@|烟花苦力怕* +creeper-confetti@|烟花苦力怕 / 烟火苦力怕* funnels|液体漏斗* advent-of-ascension-nevermine@adventofascension|虚无世界 (Nevermine) toroquest|托罗探索 (Toro Quest) @@ -1633,7 +1633,7 @@ deep-mob-learning|深度怪物学习* tofucraftreload@|豆腐工艺重制版 (TofuCraftReload) electroblobs-wizardry|巫术学 (Electroblob's Wizardry) moarsigns|更多告示牌 (MoarSigns) -kamen-rider-craft|假面骑士工艺* +kamen-rider-craft@|假面骑士工艺* avaritia-io|无尽贪婪:接口 (Avaritia IO) dense-neutron-collectors|致密中子态素收集器*¨dense-neutron-collectors-1-7|致密中子态素收集器 (Dense Neutron Collectors) pubgmc-mod @@ -1711,7 +1711,7 @@ terrarium@|地球* climatic-biomes howling-moon|狼人* hostile-worlds-invasions|敌对世界-入侵 (Hostile Worlds - Invasions) -simple-storage-network|简单存储* +simple-storage-network@|简单存储* 2973537|虚无世界1 (Eternal Isles) scaling-health@ minecoprocessors|微处理器* @@ -1741,7 +1741,7 @@ ping the-legends-mod bad-wither-no-cookie-reloaded¨bad-wither-no-cookie-reloaded-fabric giacomos-fishing-net -the-hospital-mod|医院 Mod*¨hospital-mod-facilities-pack|医院 Mod (The Hospital Mod)¨hospital-mod-theatres-pack|医院 Mod (The Hospital Mod)¨hospital-mod-pharmacy-pack|医院 Mod (The Hospital Mod) +the-hospital-mod|医院 Mod*¨hospital-mod-pharmacy-pack|医院 Mod (The Hospital Mod)¨hospital-mod-facilities-pack|医院 Mod (The Hospital Mod)¨hospital-mod-theatres-pack|医院 Mod (The Hospital Mod)¨hospital-mod-xpansions-walls-and-flooring-pack|医院 Mod (The Hospital Mod) more-charcoal|更多木炭* born-in-a-barn@ @@ -1970,7 +1970,7 @@ hearth-well tea-and-biscuits|茶和饼干* controllable|手柄控制*¨controllable-fabric|手柄控制 (Controllable) genetic-animals@|遗传动物* -stone-chest|石头箱子* +stone-chest@|石头箱子* iron-shulker-boxes@|更多潜影盒* mob-dismemberment|怪物肢解* zoo-wild-animals-rebuild|野生动物园:重制版 (Zoo & Wild Animals Rebuilt) @@ -2019,7 +2019,7 @@ tetra@ minestrappolation-5 underwater-biome|深海生态* create@|机械动力*¨create-fabric@|机械动力 (Create) -super-tnt-port|超级 TNT (Super TNT Mod) +super-tnt-port|超级 TNT (Super TNT Mod) - 授权续作及转载¨@super-tnt-mod|超级 TNT (Super TNT Mod) energeticsheep@energetic-sheep|脉冲羊 (EnergeticSheep) ensorcellation@|万象* @@ -2035,12 +2035,12 @@ modular-diversity|模块化多样性* botanic-bonsai|植物魔法盆栽* modular-magic|模块化魔法* -schools-of-magic-mod|魔法王国 (Magic Kingdoms Mod/Schools of Magic Mod)¨magic-kingdoms-mod|魔法王国 (Magic Kingdoms Mod/Schools of Magic Mod) +schools-of-magic-mod|魔法王国 / 魔法学校 (Magic Kingdoms Mod / Schools of Magic Mod)¨magic-kingdoms-mod|魔法王国 / 魔法学校 (Magic Kingdoms Mod / Schools of Magic Mod) zollern-galaxy|卓伦星系* macaws-bridges@|Macaw的桥梁 (Macaw's Bridges) trample-stopper|防止踩踏*¨trample-stopper-fabric|防止踩踏 (Trample Stopper) -macaws-roofs@|Macaw的屋顶 (Macaw's Roofs) +macaws-roofs@|Macaw 的屋顶 (Macaw's Roofs) shadows-of-greg|格雷之影 (Shadows of Greg) loottabletweaker@ rockycore @@ -2114,7 +2114,7 @@ emerging-technology-hydroponics|新兴技术 (Emerging Technology) twilightopia-phantasy|暮托邦之梦 (Twilightopia) engineers-decor|工程师的装饰 (Engineer's Decor) engineers-tools|工程师的工具 (Engineer's Tools) -wesleys-roguelike-dungeons@|Wesley的冒险地牢 (Wesley's Roguelike Dungeons) +wrd@wesleys-roguelike-dungeons|Wesley的冒险地牢 (Wesley's Roguelike Dungeons) xp-tome@|经验之书* varied-commodities tinkers-things|匠魂扩增 (Tinkers' Things) @@ -2190,7 +2190,7 @@ electro-magic-tools-ic2-classic|电子神秘工具 (Electro-Thaumic Tools - IC2 forceasciifont|强制ASCII字体 (ForceASCIIFont) skewers|烤串* get-in-the-bucket-mod@|进入水桶*¨get-in-the-bucket-mod-fabric|进入水桶 (Get In The Bucket Mod) -fertilization|施肥*¨fertilization-fabric|施肥 (Fertilization) +fertilization@|施肥*¨fertilization-fabric|施肥 (Fertilization) additional-banners@|更多旗帜* dropt the-elven-forest|精灵森林 (The Elven Forest​) @@ -2250,7 +2250,7 @@ OpenSecurity|开放式安全* the-legend-era|传说纪元* level-up-hp-forge|生命提升 (Level Up HP)¨level-up-hp|生命提升 (Level Up HP) more-planet-extras -randompatches@|随意修复 (RandomPatches)¨randompatches-fabric|随意修复 (RandomPatches) +randompatches-forge@randompatches|随意修复 (RandomPatches)¨randompatches-fabric|随意修复 (RandomPatches) ships-mod|船* tfc-metric|公制重量转换 (TFC Metric) @@ -2449,7 +2449,7 @@ better-drowning@|更好的溺水* bibliotheca kubejs@¨kubejs-fabric tickcentral -lowocalizatiwn +l12n redlib modern-ui@|现代化 UI (Modern UI) auto-config-updated-api @@ -2807,7 +2807,7 @@ minema tfctech-unofficial|群峦工业非官方版 (TFCTech Unofficial) @ee1|等价交换 (Equivalent Exchange) invmove@|边拿边走 (InvMove)¨invmove-fabric|边拿边走 (InvMove)¨invmove-forge|边拿边走 (InvMove) -autofish-for-forge|自动钓鱼Forge版 (AutoFish for (Neo)Forge) +autofish-for-forge|自动钓鱼Forge版 (AutoFish for Everyone) industrial-upgrade@industrialupgrade|工业升级* rpgstats@ datapackrecipemaker|数据包配方助手 (Datapack Recipe Maker) @@ -2829,7 +2829,7 @@ blood-in-the-water bio-technik -astromine-fabric|天体矿工 (Astromine - Machines n' Space!) +astromine-fabric|天体矿工 (Astromine) rustic-thaumaturgy|神秘乡村* thallium|铊* @@ -3206,7 +3206,7 @@ jeitweaker@ yungs-law libx@ atmospheric@|悠然一派* -monospace-font|等宽字体 (Monospace font) +monospace-font|等宽字体* mana-liquidizer@|魔力液化器* neapolitan@|那不勒斯风味* @@ -3242,7 +3242,7 @@ stare-till-they-grow|盯到它们熟为止! (Stare Till They Grow!) vm-computers|虚拟电脑 (VM Computers) inventory-free|无物品栏 (Inventory-Free) -randompatches-integration +randompatches-integration|随意修复集成 (RandomPatches Integration) bubble-column-elevator-backport|1.13水域机制 (1.13 Bubble Columns for 1.12) basic-nether-ores@|基础下界矿石* nuclearcraft-reactor-builder|核电工艺反应堆建造机 (NuclearCraft Reactor Builder) @@ -3341,7 +3341,7 @@ simple-generators@|简易发电机* tfc-bonsai|群峦盆栽 (TFC Bonsai) tfc-drying-rack|群峦晾干架 (TFC Drying Rack) -lumen|流明 (Lumen 🔥) +illuminations-legacy|流明 (Illuminations Legacy 🔥 / Lumen 🔥)¨lumen|流明 (Illuminations Legacy 🔥 / Lumen 🔥) openblocks-elevator@elevatormod|开放式电梯 (OpenBlocks Elevator / ElevatorMod) crafttweaker-integration|CrT集成 (CraftTweaker Integration) @@ -3982,11 +3982,11 @@ playtime-counter-fabric|游戏时长计数器 (Playtime Counter) entity-banners|实体旗帜* curious-jetpacks curious-armor-stands@¨curious-armor-stands-fabric -trinkets-fabric@trinkets +trinkets@ occultism@|神秘学* stray-spawn@|流浪者生成微调*¨stray-spawn-fabric|流浪者生成微调 (Stray Spawn) mooshroom-spawn@|哞菇生成微调*¨mooshroom-spawn-fabric|哞菇生成微调 (Mooshroom Spawn) -cardinal-components@cardinal-components-api +cardinal-components-api@ integrated-proxy|集成代理器* @@ -4243,7 +4243,7 @@ chinese-style-sword@arsenal-core|中式剑 (Arsenal Core) heroic-armory|英雄武器* colossal-battery|巨型电池 (ColossalBattery) industrial-meat - +biomancy@ extra-generators@|额外发电机* horticulture|园艺* mr-pineapples-food-mod @@ -4635,7 +4635,7 @@ death-coords|死亡坐标显示* never-enough-currency|货币多多* never-enough-currency-lite|货币多多:精简版* create-chunkloading -name-pain@namepain +namepain@ wireless-chargers@|无线充电器* bouncier-beds@|弹力床*¨bouncier-beds-fabric|弹力床 (Bouncier Beds) hexlands-ii|六边形地貌 2 (HexLands II) @@ -4704,7 +4704,7 @@ gamemodeoverhaul@|旧版指令 (GamemodeOverhaul) dash@ minecraft-dungeons-content@dungeons-content|地下城内容 (Dungeons Content) old-beacon|旧信标* -oc2|开放式电脑 II (OpenComputers II) +oc2@opencomputers-ii|开放式电脑 II (OpenComputers II) botany-pots-tiers@|更好的植物盆栽* compatchedstorage|紧凑存储修复 (ComPatchedStorage) iseedragons @@ -4797,7 +4797,7 @@ macaws-fences-and-walls@|Macaw 的栅栏与墙 / 麦考的栅栏与墙壁 (Macaw lunae-silva rainbow6|我的六号 (Rainbow6:Minesiege) -friendlyendermite@friendermite|友好的末影螨 (FriendlyEndermite) +friendermite@|友好的末影螨 (FriendlyEndermite) food-expansion|食物扩展* forgotten-items|被遗忘的物品* food-expansion-reimagined@|食物扩展:重构想 (Food Expansion: Reimagined) @@ -4941,7 +4941,7 @@ open-to-lan simple-translation@|简单翻译* fx-control@|效果控制 (Fx control) what-are-you-voting-for@ -steam-powered-create@create-steam-powered|蒸汽动力 (Create: Steam Powered) + creepers-burn@CreepersBurn pretty-game-enhhancing-gobblet-pgeg-remake|魔法森林重置版 (Pretty, Game-Enhhancing Gobblet Remake) @@ -4971,7 +4971,7 @@ environmental-creepers@|环保型苦力怕* look-at-that advanced-peripherals@advancedperipherals|高级外设* -nether-chest-fabric@nether-chest +better-nether-chest@nether-chest @essentialaddons levelz@ betterend-re-forked|更好的末地Forge重铸版 (BetterEnd Reforked) @@ -5119,7 +5119,7 @@ slimier-slimes stupid-weapon slime-carnage|史莱姆杀戮* portal-tags -eden-ring@eden-ring-reforked|伊甸星环* +eden-ring|伊甸星环* placement-tweaks|放置调整* kimetsu-animation-demon-slayer-animation|鬼灭动作 (Kimetsu Animation) hals-enhanced-biomes @@ -5191,7 +5191,7 @@ warp-pipes fertile-farmland|沃壤千里* luckperms@ inv-view@invview -ftb-industrial-contraptions|FTB工业奇械 (FTB Industrial Contraptions) +ftb-industrial-contraptions-forge|FTB工业奇械 (FTB Industrial Contraptions) more-respawn-anchors|更多重生锚*¨more-respawn-anchors-forge|更多重生锚 (More Respawn Anchors) werewolves-become-a-beast@werewolves|狼人 (Werewolves) omnipotent-card@|万用卡牌* @@ -5242,7 +5242,7 @@ lost-and-found better-fps-render-distance¨better-fps-render-distance-fabric armor-chroma-for-fabric@|盔甲颜色显示Fabric版 (Armor Chroma for Fabric) ore-reeds|矿石甘蔗* -connected-glass@ +connected-glass@|玻璃连结* blockui storage-racks Dung-Pipe @@ -5380,7 +5380,7 @@ multiblocktweaker good-ol-currency macaws-bridges-atmospheric|Macaw 的桥梁:悠然一派附属 (Macaw's Bridges - Atmospheric) leadvillagers -fabric-shield-lib@fabricshieldlib +shield-lib@shieldlib frys-things-guns-and-more quickbind|功能集成 (Quick Bind) ompd|开放式模块化被动防御 (Open Modular Passive Defense) @@ -5458,7 +5458,7 @@ mc-mods-the-blocks mc-modsthemodium ato just-enough-effect-descriptions-jeed@|JEI药水效果 (Just Enough Effect Descriptions) -hostile-neural-networks@ +hostile-neural-networks@|敌对神经网络* mo-cakes|更多蛋糕 (Mo' Cake) fishing-made-better|更好的钓鱼* neon-craft-ultimate|霓虹灯艺2 (Neon Craft Ultimate/Neon Craft 2 Mod) @@ -5615,7 +5615,7 @@ windowed-fullscreen helpful-hitboxes-forge¨helpful-hitboxes-fabric vein-mining@|连锁采集*¨vein-mining-fabric|连锁采集 (Vein Mining) ars-creo@ -it-fell-from-the-sky +it-fell-from-the-sky|天降之物* @@ -5633,7 +5633,7 @@ extended-armor-bars atom-economy|基石-经济 (Atom-Economy) just-enough-piglin-bartering|JEI猪灵以物易物* -@aftersans53228-fabric-road|暗访谈的Fabric道路 (aftersans53228's fabric roads) +@aftersans53228-fabric-road|AFRoads路建 (AFRoads Construction) primal-magick@|原初魔法* ore-creeper@|矿石苦力怕* panthalassa|泛古洋* @@ -5669,7 +5669,7 @@ biome-id-extender rereskillable-reborn rereskillable-rereforked tetra-pak -ars-magica-legacy@ +ars-magica-legacy@|魔法艺术:遗产 (Ars Magica: Legacy) compressium talking-villagers|会说话的村民* artifality@ @@ -5678,7 +5678,7 @@ delogger just-another-vanilla-addon-java|迫真原版增强 (Just Another Vanilla Addon) spectrite-mod|彩虹 (Spectrite) genshin-nature@|原神:自然* -power-utilities-remastered@power-utilities|能源转换重制* +power-utilities-iu@power-utilities|能源转换重制 (Power Utilities Remastered) create-gear-addon gun-mod-tl @@ -5689,7 +5689,7 @@ simply-quarries@|简易采石场* lethal-peaceful -storage-drawers-crafting-recipe +storage-drawers-crafting-recipe|储物抽屉合成配方* enhanced-attack-indicator@|增强攻击提示器* parasites-addon-overlast|寄生兽:末世扩展 (OverLast) cancel-block-update@|取消方块更新* @@ -5698,15 +5698,15 @@ d3core green-thumb-v1-2-03 greenthumb tfc-adventure|群峦冒险 (TFC+ Adventure) -just-zoom-forge@just-zoom +just-zoom@ server-holograms@holograms|全息图 (Holograms) eidolons-hexblades|幻梦咒剑 (Eidolon's Hexblades) monke-madness put-ingot-down|把锭放在地上吧! (Put Ingot Down!) torchtools -lottweaks -quests-additions-fabric|任务拓展 (Quests Additions)¨quests-additions|任务拓展* +lottweaks@ +quests-additions|任务拓展*¨quests-additions-fabric|任务拓展 (Quests Additions) custom-mob-spawner|自定义生物生成器* rpg-hud@¨rpg-hud-fabric the-wild-update-concept-mod @@ -5716,7 +5716,7 @@ oreregen|再生矿石 (OreRegen) eidolon_tweaker herodotusutils@herodotus-utils -platypuses +platypuses|鸭嘴兽* hostile-villages|敌对村庄* throwability|投掷* biomeinfo@|生物群系信息 (BiomeInfo)¨biomeinfo-fabric|生物群系信息 (BiomeInfo) @@ -5771,7 +5771,7 @@ psicaster tweakermore@ legacy-display@ the-backrooms -creatures-of-the-snow@creatures-from-the-snow-fabric|雪地里的生物! (Creatures From The Snow!)¨creatures-from-the-snow-forge|雪地里的生物! (Creatures From The Snow!) +creatures-from-the-snow-fabric@|雪地里的生物! (Creatures From The Snow!)¨creatures-from-the-snow-forge|雪地里的生物! (Creatures From The Snow!) collisions-lib@ wooled-boots@|羊毛靴子* zawa-evolved@|野生动物园:进化 (Zoo and Wild Animals: Evolved) @@ -5786,12 +5786,12 @@ death-compass-forge|死亡指南针 (Death Compass)¨death-compass-fabric|死亡 portable-stonecutter|便携式切石机* enchanted-book-redesign@¨enchanted-book-redesign-fabric -quick-hotbar +quick-hotbar|快捷工具栏* modular-augment lumberjack better-mods-button@|更好的模组按钮* -advancement-frames@ +advancement-frames@|进度展示框* dynmapforge spellbind@|物品魔法* @@ -5840,13 +5840,13 @@ feature-nbt-deadlock-be-gone@ real-infinity-bow-allow-inf-mending|真正的无限弓 (Real Infinity Bow) drink-it-forge|喝! (Drink It!) foxlib -tails +tails|尾巴* howling-moon-rebooted-forge¨howling-moon-rebooted-fabric @dustrial-decor furnish@furnish-furniture -iammusicplayer@iam-music-player +iammusicplayer@iam-music-player|Iam 音乐播放器 (Iam Music Player) pirates|海盗* comics-bubbles-chat@ @@ -5861,8 +5861,8 @@ orderly-fabric-2-0 entity-texture-features-fabric@entitytexturefeatures|实体纹理特性 (Entity Texture Features) sculk-plus -creeper-confetti-fabric|烟花苦力怕Fabric版* -double-jump-attribute@ +creeper-confetti-fabric|烟花苦力怕 Fabric 版* +double-jump-attribute@doublejumpattribute overweight-farming@ healthcare@ betterloadingscreen|加载画面改良 (Better Loading Screen) @@ -5883,7 +5883,7 @@ worldinfo @bang block-helper@ @class-loader-fixer -@font-fixer +@font-fixer|字体修复 (Font Fixer) gregicprobe travelers-index @@ -5904,13 +5904,13 @@ tinkers-golems-addon tinkers-addons-fork reagenchant -sim-u|模拟城市/模拟大都市 +sim-u|模拟城市/模拟大都市 (Sim-U) thorium@|钍* creative-one-punch@ radon@|氡* ars-omega -game-stages-conditions +game-stages-conditions|游戏阶段条件* tinkers-construct-tool-leveling-with-random odd-water-mobs glowsquids-begone@ @@ -5943,7 +5943,7 @@ custom-fluid-mixin@ slender-reimagined doors-by-ichun slender-man-mod¨the-original-slenderman-mod-working|Slender Man - 他人的非官方重上传 -cookielicious|曲奇佳味* +cookielicious@|曲奇佳味* the-forbidden|禁忌者* memory-usage-screen@ evergreenhud@ @@ -5969,7 +5969,7 @@ keygrip-mocap-mod attachable-grinder hat-stand -chunk-copy-fabric@ +chunk-copy-fabric@|区块复制 (Chunk Copy) inventory-home|回归箱* nether-hexed-kingdom @@ -6116,7 +6116,7 @@ simple-discord-rpc@ excore messmod@ -log4j2-jndi-exploit-fix +log4j2-jndi-exploit-fix|Log4J2 JNDI 漏洞修复 (Log4J2 JNDI Exploit Fix) woodcutter¨woodcutter-forge origins-classes-forge@|起源:职业非官方Forge版 (Origins: Classes (Forge)) tool-stats@ @@ -6525,7 +6525,7 @@ easyexcavate wooden-shears@|木剪刀* brick-furnace@|砖熔炉* brick-hopper@|砖漏斗* -human-companions +human-companions|人类同伴* odysseyhud colorblindness@ aether-addon @@ -6685,7 +6685,7 @@ geode realistic-spiders|真实的蜘蛛* @timedisplay|时间显示 (Time Display) biome-particle-weather@biome_particle_weather -ftb-backups-2|FTB备份2 (FTB Backups 2) +ftb-backups-2|FTB 备份 2 (FTB Backups 2) darkkore@ @@ -6833,7 +6833,7 @@ recipe-gui autotechno@ villager-hats-mod@villager-hats custom-damage-particles@|自定义伤害粒子* -@aei|AEI物品管理器 (Always Enough Items) +@aei|AEI 物品管理器 (Always Enough Items) cyan-warrior-swords-mod@ plant-in-a-jar@ solid-mobs@solid_mobs @@ -7031,7 +7031,7 @@ inmis@¨inmis-forge rechiseled@ durability-tooltip@ -oreganized-mod|井然有矿* - 旧版¨oreganized|井然有矿 (Oreganized Mod) - 2.0+ 版¨@oreganized|井然有矿 (Oreganized Mod) +oreganized|井然有矿 (Oreganized Mod) - 2.0+ 版¨oreganized-mod|井然有矿* - 旧版¨@oreganized|井然有矿 (Oreganized Mod) riding-utilities compact-help-command@¨compact-help-command-fabric @@ -7096,7 +7096,7 @@ nock-enough-arrows every-compat@|泛用兼容:木材 (Every Compat (Wood Good)) offline-mode ghastly-wail@ -immersive-weapons#c71@immersive-weapons|沉浸式武器 (Immersive Weapons) +immersive-weapons@|沉浸式武器* clear-skies@ @@ -7404,7 +7404,7 @@ roughly-enough-trades@ simple-quarry@ magitek-mechs|Magitek Mechs - 已停用¨@magitek-mechs infectum -nocubes-sea-dwellers +nocubes-sea-dwellers@aures-sea-dwellers show-me-your-skin@ invulnerable-pigs t88@ @@ -7463,7 +7463,7 @@ hookshot@ custom-spawns create-misc-and-things@|机械动力:实用物件 (Create : Misc & Things) low-fire@ -infinity-buttons@infinitybuttons|无限按钮* +infinitybuttons@|无限按钮 (Infinity Buttons) i-know-what-im-doing-fabric skin-hotkey@ dyeable-fishing-lines-fabric@dyeable-fishing-lines¨dyeable-fishing-lines-forge @@ -7488,7 +7488,7 @@ playeranimator@ apathy-mod@apathy auudio-forge@auudio¨auudio-fabric -easy-shulker-boxes@¨easy-shulker-boxes-fabric +easy-shulker-boxes@|简单潜影盒*¨easy-shulker-boxes-fabric|简单潜影盒 (Easy Shulker Boxes) qu-enchantments@ ironchests@ @@ -7501,7 +7501,7 @@ noits-simple-sorting colds-gauntlets level-one@ wilder-wild@ -paintings-with-dragons +paintings-with-dragons@aures-paintings-with-dragons more-redstone-lamps|更多红石灯* placeable|可放置植物 (Placeable Plants) tfc-hot-or-not@ @@ -7906,7 +7906,7 @@ echoing-depths advanced-finders maple-forest|枫林* -@legacyinputpatch|Legacy Fabric 输入补丁 (LegacyInputPatch) +@legacyinputpatch|Legacy Fabric 输入补丁 (Legacy Input Patch) droplight xanders-sodium-options¨@xanders-sodium-options|Xander's Sodium Options - 已归档 entity-view-distance@ @@ -7966,7 +7966,7 @@ visual-overhaul@ drawerfps-legacy|储物抽屉渲染配置:遗产 (DrawerFPS Legacy) status-effect-timer@statuseffecttimer logical-zoom@ - +no-trampling-on-farmland@|阻止踩踏农田 (No trampling on farmland) the-space-age-mod|太空时代* touhou-origins@|起源:东方 Project 附加包* earth-mobs@earthmobs|地球生物* @@ -8104,7 +8104,7 @@ toomanyorigins@ evil-regeneration@ food-treasure-box|食物宝盒* revolution -apugli¨@apugli +apugli@ harvest-season @ic2fix @extended-bookshelves @@ -8125,7 +8125,7 @@ id-primeval@primeval colorfulazaleas@colorful-azaleas|杜鹃缤纷 (Colorful Azaleas) pigeon-post@ hunted-api - +kablade|斩无不断 (Ka Blades) bigger-structures find-my-friends@ @@ -8178,7 +8178,7 @@ spice-of-life-apple-pie-edition@|生活调味料:苹果派版 (Spice of Life: xp-obelisk@|经验方尖碑 (XP Obelisk) enhanced-searchability@|搜索增强* compact-ui@ -lets-do-wine@lets-do-vinery|葡园酒香 (Let's do Wine / Vinery) +lets-do-wine@lets-do-vinery|葡园酒香 ([Let's Do] Vinery) wealthyandgrowth@ subtitle-highlight@|字幕高亮* clockhud@clock-hud @@ -8204,7 +8204,7 @@ compassplus create-industry@create-tfmg|机械动力:工业长路 (Create: Industry/Create: The Factory Must Grow) simple-splash-screen@ unusual-prehistory-forge@ -biospheres|生物圈 (Biosphere) +biospheres|生物圈* qkl@ dynamic-trees-tech-reborn|动态的树:科技复兴附属 (Dynamic Trees - Tech Reborn) @@ -8663,7 +8663,7 @@ sorcerium@ zeropoint campful -well-rested@ +well-rested|Well Rested - ,此指向的并非本¨@well-rested raw-input-1-12-2@raw-input spider-pig jingames-hair-c@hair-c @@ -8719,7 +8719,7 @@ rpg-rpgae|RPG天启进化 (RPGApocalypticEvolution) dungeon-difficulty@ cazfps-chronicles -unstable-tools@unstable|不稳定金属工具* +unstable-tools@|不稳定金属工具* tech-reborn-patchouli biome-bundle moon-phase-info@ @@ -8921,7 +8921,7 @@ spellblade-next@spellblades-and-such netherite-road-2@|下界之路2* @tileentity-reset-fix notenoughrecipebook@ -urushi-mod|漆 (Urushi) +urushi-mod@urushi|漆 (Urushi) nicephore¨nicephore-fabric¨@nicephore terraria-paintings¨terraria-paintings-fabric minegooseduck @@ -8943,7 +8943,7 @@ no-weather-effects@ ob-tooltips hekate-lib loot-journal@¨loot-journal-neoforge¨loot-journal-fabric - +flans-mod-ultimate-1-1-modified @@ -9112,7 +9112,7 @@ chatimage@|聊天栏图片显示 (ChatImage) @treasurehunt|寻宝 (TreasureHunt) no-tema-stahp labels@|存储标签 (Storage Labels) -@qdaa +qdaa@ better-creeper-mod|更好的苦力怕 (Better Creeper) regions-unexplored@|未至之地* @@ -9167,7 +9167,7 @@ wood-stuff magmafurnace-weapons-bosses-and-more monstersregulararmy why-does-my-food-taste-like-that -iron-generators +iron-generators@ elytra-swapper-forge|鞘翅切换* ftb-team-islands-forge @@ -9203,7 +9203,7 @@ mob-captains|怪物队长* snowballing winter-essentials@winter_essentials embeddium-extras@|镁/铷附属:土豆版 (Magnesium/Rubidium Extra: Potato Editon*) -@realistic-cruelty|真实残忍 (Realistic Cruelty) +realisticcruelty@|真实残忍 (RealisticCruelty) trapper-pelts@ workshops-of-doom@ create-mechanical-spawner@|机械动力:动力刷怪笼* @@ -9213,7 +9213,7 @@ fzzy-core@ tnt-breaks-bedrock@ from-the-north@ spruce-willis-the-xmas-tree@ -sweet-potato-core-spc|地瓜核心 (Sweet Potato Core) + merge-enchantments@ turkey|火鸡* @@ -9226,7 +9226,7 @@ villager-comfort|村民舒适度* ender-quarry|末影采石场* -mythic-upgrades-fabric@mythic-upgrades¨mythic-upgrades-forge +mythic-upgrades@ fat-chicken snowmancy@ @@ -9374,7 +9374,7 @@ item-info florist omni frostrealm@ -nameless-trinkets@ +nameless-trinkets@|无名饰品* unsafe-spawns bubbles@pop @@ -9685,7 +9685,7 @@ tfc_metalwork|群峦金属加工 (TFC Metalwork) furniture-compatibility-for-macaw-tfc|Macaw 的家具:群峦传说附属 (Furniture Compatibility for Macaw/TFC) -roof-compatibility-for-tfc-macaw|Macaw的屋顶:群峦传说附属 (Roof Compatibility for TFC/Macaw) +roof-compatibility-for-tfc-macaw|Macaw 的屋顶:群峦传说附属 (Roof Compatibility for TFC/Macaw) tfc-supplementaries|群峦锦致装饰 (TFC Supplementaries) tfc-ie-crossover|群峦传说:沉浸工程兼容附属 (TFC + IE Crossover) @@ -9916,7 +9916,7 @@ world-of-bosses mirabilis-vanilla-biome-overhaul energized-power@ carpenter -tolerable-creepers +tolerable-creepers@ anchor locksmith mystical-machinery|神秘机械* @@ -9965,7 +9965,7 @@ bring-color-to-my-skies portal-dungeons radiant-gear@¨radiant-gear-fabric -create-teleporters@|机械动力:传送器 (Create : Teleporters) +create-teleporters@|机械动力:传送器 (Create: Teleporters) enchantato floating-block@floating-cube|浮空立方体 (Floating Cube) curious-lanterns@ @@ -9987,7 +9987,7 @@ fog-looks-good-now@ guarding@ cats-expanded@ modular-bosses -experience-obelisk@cognition +experience-obelisk@cognition|认知 (Cognition*) here-be-no-dragons gnumus-settlement fading-night-vision @@ -10078,7 +10078,7 @@ completionists-index@ stoneworks@ mob-lassos@|生物套索* -mutant-monsters@ +mutant-monsters@|突变怪物* @physics-toys tarot-cards@|塔罗牌* @@ -10171,7 +10171,7 @@ blue-archive-halos @totemcounter @mana-visualizer -mhcraft +m3h irons-spells-n-spellbooks@|Iron的法术与魔法书 (Iron's Spells 'n Spellbooks) infernal-enchantments reimagined-world-menu@ @@ -10202,7 +10202,7 @@ end-origins extra-classes-origins wire-decoration|电线装饰* optimizationsandtweaks@ - +the-flesh-that-hates@tfth tabfaces@ mob-armor-mod-v2@mob-armor-mod-v3 umbral-skies @@ -10413,7 +10413,7 @@ openblocks-trophies@ @shrug-it-off @f3-t-everywhere -lootbag-patches +lootbag-patches@ stargate-journey@sgjourney|星门之旅* xaero-arrow-fix|Xaero Arrow Fix - 无法¨@xaeroarrowfix @@ -10746,7 +10746,7 @@ classic-minecraft-icon@ some-useful-ores @stardew-valley-mod rainy-harvest -mythic-mobs@mobs-of-mythology +mobs-of-mythology@ lost-features@lostfeatures bagus-lib@ @@ -10792,7 +10792,7 @@ industrybase|工业基石 (IndustryBase) silent-river-in-the-nether|炎液断蒸* ramel-reforged sulfar-mod -apple-cows +apple-cows|苹果牛* gardening-tools@ nether-ores-plus river-treasures @@ -11063,7 +11063,7 @@ yeetusexperimentus@yeetus-experimentus xbob@|准星摇晃 (Crosshair Bobbing) zombie-horse-spawn@|僵尸马生成微调*¨zombie-horse-spawn-fabric|僵尸马生成微调 (Zombie Horse Spawn) tinkers-katanas|匠魂武士刀 (Tinkers' Katanas) -fbp +fbp@ yeetem-potions@ incapacitated@ @fys|锻汝砧上 (Forge Yourself) @@ -11089,7 +11089,7 @@ moa-decor-cookery moa-decor-science create-guardian-beam-defense antisocial@ -expanding-technologies@expandingtechnologies +expandingtechnologies@ despawning-eggs-hatch@ mob-death-messages chunkapi@ @@ -11200,7 +11200,7 @@ placeable-mobs@placeble-mobs potion-blender@ t6-auto-attack-mod@t6-auto-attack forceasciifont-backport -vmtranslationupdate@vmupdate|VM汉化更新 (VMTranslationUpdateMod) +vmtranslationupdate@vmtu|VM汉化更新 (VMTranslationUpdateMod) trample-no-more@ jeremyseq-damage-indicator@jeremyseqs-damage-indicator @@ -11531,7 +11531,7 @@ mother-silverfish mcretector@ little-botanics -ex-pattern-provider@extended-ae|AE2扩展 (ExtendedAE) +ex-pattern-provider@extended-ae|AE2扩展 (Extended AE) @integrated-circuit creative-disk engineers-construct @@ -11610,7 +11610,7 @@ bloxys-structures@ enhanced-hordes niftycarts@ wither-dimension-whitelist -@hexlink +hexlink@ usefulfood-reborn@|有用的食物 重生 (UsefulFood Reborn)¨usefulfood-community|有用的食物 重生 (UsefulFood Reborn) ancient-warfare-2-tweaked@ @@ -11644,7 +11644,7 @@ simply-skills@ -cctv-craft@cctvcraft +cctvcraft@ redeco@|重铸家具 (Re:Deco) pocket-storage animated-player-mod|动画化玩家* @@ -11677,7 +11677,7 @@ dirtnt@|泥土TNT (DirTNT) equipable-twilight-forest-charms-tfc|可装备的暮色森林符咒 (Equipable Twilight Forest Charms) buriedwrecks@buried-wrecks zapsconverter|Zaps 转换器 (Zaps Convertor) -amethyst-update +amethyst-update@ angel-block|天使方块* angel-block-renewed@ da-bomb@ @@ -11730,7 +11730,7 @@ constructs-armory-1-18-2-port@construct-armory-updated @videotape @enchantmenttweaker -let-me-sleep +let-me-sleep|让我睡觉* forge-tooltip-texts-scroller¨fabric-tooltip-texts-scroller well-behaved-mobs @@ -11952,7 +11952,7 @@ ahznbs-naruto-mod-addon random-village-names@¨random-village-names-fabric mooshroom-tweaks@ grinners-ents -log-cleaner@ +log-cleaner@|日志清理* lunar@ cosmetics@cosmetica @@ -12258,7 +12258,7 @@ qraftyfied theft-mod@theft akkamaddis-golden-glitter@ @obsidianbucket -enhanced-annihilation-planes +enhanced-annihilation-planes@ akkamaddis-simple-cthon@ @heads-hurt-too! pooping-animals @@ -12588,7 +12588,7 @@ better-chunk-loading-forge-fabric mushroomified soul-like-boss-epic-fight-addon -moderately-enough-effect-descriptions-meed-a-jeed-addon@moderately-enough-effect-descriptions-(meed)-jeed-addon¨@moderately-enough-effect-descriptions-meed|Moderately Enough Effect Descriptions - 新,未及时更新 +moderately-enough-effect-descriptions-meed@moderately-enough-effect-descriptions-(meed)-jeed-addon¨@moderately-enough-effect-descriptions-meed|Moderately Enough Effect Descriptions - 新,未及时更新 alexs-caves@|Alex 的洞穴 (Alex's Caves) bow-spam-be-gone@ lmdcraft @@ -12640,7 +12640,7 @@ eager-beavers@ laendli-transport witchs-artifacts the-pumpkin-challenge@ -loot-crates-plus@loot+ +loot-bags-plus@loot+ fuji@|藤* legendary-origins@ @@ -12782,7 +12782,7 @@ creatures-of-runeterra@cor from-another-world@ pumpkin-moon@ zombie-onslaught@ -neighborly +neighborly@ natural-progressions@natural-progression charged-explosives@ pulver@ @@ -12804,7 +12804,7 @@ celestial-enchantments|星月附魔* clearsky|洁净天空 (ClearSky) educate-nitwit damage-spawns-mobs -decorative-sniffer-egg@ +decorative-sniffer-egg@decorative-sniffer-egg-old gilded-sherds@ camps-castles-carriages rainbow-reef@ @@ -13201,7 +13201,7 @@ cenozoic-nightmare-the-horror-of-prehistory quickbackupmulti@|多槽位备份 (QuickBackupMulti) slashblade-japanese-addon-pack-reforked|拔刀剑日系附属重置版 (SlashBlade Japanese Addon Pack Reforked) -@create-fabric-fixes +@create-fabric-fixes|机械动力 Fabric 修复 (Create Fabric Fixes) @command-block-sceptre|命令方块权杖 (Command Sceptre) crate-delight-forge@crate-delight|装箱乐事 (Crate Delight)¨crate-delight-fabric|装箱乐事 (Crate Delight) cultural-creators-forge-create-and-cultural@cultural-crators¨cultural-creators-fabric-create-and-cultural @@ -13249,7 +13249,7 @@ digestive inventory-binds@ -octo-lib@ +shatterbyte-lib@ farmers-plus¨farmers-utils|Farmer's Plus - 旧 architects-friend bagus-cosmetic@ @@ -13294,13 +13294,13 @@ harder-wildlife|更难的野生动物* harder-farming|更难的农作* waterproof entityculling-unofficial@entitycullingunofficial|实体渲染机制优化:非官方版 (EntityCulling Unofficial) -@sync-projecte-knowledge +sync-projecte-knowledge@ eventtweaker craft-vania lotrextracoins-for-1-7-10|额外魔戒银币 ([Legacy] LotRExtraCoins) genst -ae2-network-analyser +ae2-network-analyser|AE2网络分析仪 (AE2 Network Analyser) mxtune customui|自定义UI (CustomUI) deadly-monsters-patched @@ -13416,7 +13416,7 @@ everyxdance@|一起跳舞! (EveryXDance) applied-flux@appflux|应用通量* tics-delight@tinkers-delight|匠魂乐事 (Tinkers' Delight / TiC's Delight) -unnamed-desert-structures@unnamed-desert +unnamed-desert@ bloomingnature@lets-do-bloomingnature|绽芳自然 (Let's Do: BloomingNature) waila-inhibitors@ thingamabobs-and-doohickeys @@ -13439,7 +13439,7 @@ better-azalea tetratic-combat-expanded@ more-beautiful-slabs@ -tools-complement-create@create-complement +create-complement@ biome-beans cats-workshop @@ -13676,7 +13676,7 @@ helpfixer drippedout@dripped-out easter-rabbit-life@ sticker-frames@ -tnt-slimes@ +tnt-slimes@|TNT 史莱姆 (TNT Slimes) armored-redstone@ bismuth-colors@bismuth-2|铋 (Bismuth) killbind@ @@ -13772,7 +13772,7 @@ lavender-api@lavender cheatutils jump-sounds@ kirin@ -llamapalooza +llamapalooza@ instant-drown acsguis game-phase@|游戏阶段* @@ -14042,7 +14042,7 @@ melee-ex-machina-meexma@melee-ex-machina arc@ cores@ -citymod-traffic-and-life@citymod|我的城市:交通与生活 (CityMod:Traffic and life) +citymod-traffic-and-life|我的城市 (CityMod) - 停止维护¨@citymod|我的城市 (CityMod) too-hard-to-dig@|隐岩砥凿 (Too Hard to Dig) simplecobblestonegenerator@simple-cobble-gen natural-baby-animals @@ -14356,7 +14356,7 @@ per-aspera@ spice-of-life-valheim-edition@mKalCkNV tfc-paths binkers-bonstruct -ranged-weapon-api@ +ranged-weapon-api@|远程武器 API (Ranged Weapon API) numismatics@ @@ -14377,7 +14377,7 @@ enchantment-enhancements@ mo-enchantments soul-like-enchantment -@blockbench-import-library +blockbench-import-library¨@blockbench-import-library|Blockbench Import Library - 不再更新 mrcrayfish-furniture-mod-rebuilt btfixes vmfixes @@ -14483,7 +14483,7 @@ additional-attributes@ suicide-key -uranus@ +uranus¨@uranus|Uranus - 不再更新 annotationlib@ @waxed nether-overload @@ -14531,7 +14531,7 @@ grieflogger@ the-quest-of-entity-303 pineapple-mending|经验修补:凤梨版* -@quick-chat|快捷聊天 (Quick Chat) +quick-chat@|快捷聊天* magic-vibe-decorations@ valentines-blessing@valentines-blessing-lilypads-roses decent-biomes@ @@ -14616,7 +14616,7 @@ creature-whisperer create-molten-metals@ -create-optical|机械动力:光学*¨@create-optical|机械动力:光学 (Create Optical) - 不全 +create-optical@|机械动力:光学* @farmers-delight-plus @@ -14726,7 +14726,7 @@ achievement-detection-fix@|成就检测修复* @myceliumlib|菌丝 (Mycelium Lib) - +@xp-formula|经验公式 (Xp Formula) cataclysmic-illagers witcherypatch@|巫术补丁 (WitcheryPatch) pin @@ -15004,7 +15004,7 @@ what-is-stone-colorful-caves@what-is-stone-(colorful-caves) vtones heavy-inventories|物品负重* vs-addition@|瓦尔基里:附加 (VS Addition) -gravity-torch@ +gravity-torch@|重力火把* lifesteal-forge-fabric@lifesteal-mian mores-by-its-infinite dragonite-gear @@ -15195,7 +15195,7 @@ winter-ambience dralards-rock-candy-mod - +vineplicate@|生蔓聚解* explosion-breaks-no-block@|爆炸不破坏方块* via-romana@ lithic-coins @@ -15461,7 +15461,7 @@ aromatic@ onekeyminer-nf@onekeyminer_nf|一键连锁(Neo)Forge (OneKeyMiner(Neo)Forge) retargetato@|约谊敌众 (ReTargetato) -iceandfire-ce@|冰火传说社区版 (IceAndFire Community Edition) +iceandfire-ce|冰火传说社区版 (IceAndFire Community Edition)¨@iceandfire-ce|冰火传说社区版 (IceAndFire Community Edition) - 不再更新 tomanos-vehicle-mod ancient-sands@ hs-bosses @@ -15598,7 +15598,7 @@ sparkle@ villagers-inventory removehud@ @orthocamera|正交相机 (OrthoCamera) -third-person-crosshair-fabric +third-person-crosshair-fabric@third-person-crosshair @hitrange black-moon@ dynamic-fire-overlay@ @@ -15688,7 +15688,7 @@ datapack-breakpoint@datapack-debugger|断点调试 (Datapack Debugger) frosted-heart|冬季救援:寒霜之心 (The Winter Rescue: Frosted Heart) create-planetary-tweaks@cp-tweaks entris@ -@sushi-bar +@bento-box lumi@lumi1710 pet-the-frog@frog-petting ex-nihilo-extras@ @@ -15712,7 +15712,7 @@ block-parry-dodge fargos-talismans@|Fargo的护身符 (Fargo's Talismans) toostrongmonster simplystronger -tacz-durability¨@tacz-durability|TACZ: Durability - 停更 +tacz-durability@ bigger-lightning-storms eternal-limbo legoman @@ -15781,7 +15781,7 @@ armor-of-the-ages@|时代铠甲 (Armor of the Ages) -@betterf3-stationapi +@betterf3-stationapi|更好的F3 StationAPI (BetterF3 StationAPI) ftb-quest-localizer|FTB Quest 本地化 (FTB Quest Localizer) modpack-update-checker@ minimotd-fabric @@ -15820,12 +15820,12 @@ structure-expansion@ armored-doggo@ the-newest-goatman epic-mob-siege-nightmare -jupiter@ +jupiter¨@jupiter|Jupiter - 不再更新 airport just-dandy@ @uk-roads my-totem-doll@my_totem_doll -void-power-for-fabric|虚空动力 (Void Power)¨voidpowermod|虚空动力 (Void Power) +voidpowermod|虚空动力 (Void Power)¨void-power-for-fabric|虚空动力 (Void Power) dis-enchanting-table@ @@ -15909,7 +15909,7 @@ reairdrop-supply@|空投供应:重生 (ReAirdrop Supply) @simply-create-model-mod|简单动力 (Simply Create Model) mountains-poem@|青丘辞 (Mountain's Poem) @playtime-reborn|游戏时长计数器:重生 (Playtime Reborn) -@flavorimmerseddaily|烟火凡人心 (Flavor Immersed Daily) +flavor-immersed-daily@flavorimmerseddaily|烟火凡人心* moon-between-medicine|在月亮之间:药学拓展 (Moon Between : medicine) craftable-chainmail-reborn|可制作的锁链甲:重生* @@ -16012,7 +16012,7 @@ scholar@ tacz-bullet-proof-enchant-add-on@¨tacz-bullet-proof-enchant-fabric-add-on no-screen-bobbing@viewbobbingmod more-spell-attributes-more-magic-series@ -minos-particle-presets +palette-lib somewhat-hardcore-fabric-forge@you-die-you-lose-a-heart @blurred-window player-list-heads @@ -16271,13 +16271,13 @@ lets-do-addon-corn-expansion@|玉食俱馩 ([Let's Do Addon] Corn Expansion) @client-maps dragon-gear-renewed -@archers-plushies +archers-plushies@ @hephaestus-expansion create-structures-arise@ kilt@ gregtech-extended-chemistry-extended ulv-covers-modern -farmers-structures +farmers-structures@aures-farmers-structures @minecartspeeddisplay|矿车速度显示 (MinecartSpeedDisplay) crossstitch@ @@ -16286,7 +16286,7 @@ free-camera-api@|自由相机API (FreeCameraAPI) @flashback bloodmagic-meteors-jei-support|血魔法陨星显示 (BloodMagic Meteors JEI Support) limited-damage-indicator-particle@|伤害粒子限制* -netherite-extension@netheriteextension|下界合金拓展续作* +netherite-extension|下界合金拓展续作*¨@netheriteextension|下界合金拓展续作 (Netherite Extension) - 不再更新 immersive-farming@|沉浸农业* @vpb-too-many-guns death-angels|死亡天使* @@ -16301,7 +16301,7 @@ terramity@ -neptune-lib@neptune +neptune-lib¨@neptune|Neptune - 不再更新 sign-edit-lite@signeditlite|告示牌编辑轻量版* @book-formatting @morewaterlogging @@ -16331,7 +16331,7 @@ hardcore-revive-mod@ petrols-parts@|Petrol 的机件 (Petrol's Parts) -slavic-delight@ +slavic-delight@|斯拉夫美食* aquamirae-delight@|海灵乐事* dumplings-delight-rewrapped@ my-nethers-delight-refabricated@ @@ -16351,7 +16351,7 @@ itemindicator@ kenshiro-mod|北斗神拳* roamers @legacycaneditsign -title-screen-mobs@ +title-screen-mobs¨@title-screen-mobs|Title Screen Mobs - 不再更新 antiblocksrechiseled@ @@ -16426,7 +16426,7 @@ linear-xp-forge|验依定值/线性经验 (Linear Xp Curve) dumplings-delight-reload@|饺子乐事:重启* -auto-switch-elytra@autoswitchelytra|自动切换鞘翅* +auto-switch-elytra|自动切换鞘翅* gregtech-placeable-emitters@ @@ -16518,7 +16518,7 @@ tct-core@ obscure-tooltips-fix @notica - +mountain-sea-and-continent-three|山海大陆3 (Mountain Sea and Continent Three) zhonglv-12et@|钟吕十二律 (ZhongLv-12ET) @@ -16592,7 +16592,7 @@ moremcmeta-emissive-forge@moremcmeta-emissive¨moremcmeta-emissive-fabric @fantazia falling-attack lesraisins-armor@ -goety-spillage@ +goety-spillage@|诡厄溢出 (Goety & Spillage) waifucraft sign-to-chat|阅言得知/聊天栏读取告示牌 (Sign to Chat) rainimatormod@ @@ -16605,11 +16605,11 @@ hardcore-revive-plus watson@ emerald-matcha-craft-v3|翠茶工藝 (Emerald Matcha Craft) -cctv-camera +cctv-camera@cameracraft trash-compactor@ rustic-delight@|乡村乐事* total-assault¨@total-assault|Total Assault - 已弃用 -sky-breaker|天穹突破* +sky-breaker@|天穹突破* @scala3std @hoofprint @@ -16628,7 +16628,7 @@ cobblegen-galore|方块制造器* create-sky-village create-crushing@ cushy-pillows|舒适枕头* -ice-and-fire-patcher@iaf-patcher|冰火传说随意修复* +ice-and-fire-patcher|冰火传说随意修复*¨@iaf-patcher|冰火传说随意修复 (Ice And Fire Patcher) - 不再更新 goety-revelation@|诡厄巫法:启示录 (Goety: Revelation) the-mandela-catalogue-alternates@ the-craftela-catalogue @@ -16644,7 +16644,7 @@ better-modlist-neoforge@better-modlist|更好的模组列表 (Better ModList) sky-aesthetics@ headshot-bonus|已无可惧 / 简单爆头* enchantlevelbrek@enchantmentlevelbreak|附魔等级上限突破2 (EnchantmentLevelBreak) -the-ruler@ +the-ruler@gamerule-manager biome-borders-revived-liteloader-only chunkborders|区块边界 (ChunkBorders) burnt@ @@ -16691,7 +16691,7 @@ patpat modern-life-patch@modernlifepatch|摩登生活补丁* @ivr rusticdelight-refabricated@|乡村乐事:重织 (RusticDelight: Refabricated) -scape-and-run-meshi@|逃逸:寄生饭 (Scape and Run: Meshi) +scape-and-run-meshi|逃逸:寄生饭 (Scape and Run: Meshi)¨@scape-and-run-meshi|逃逸:寄生饭 (Scape and Run: Meshi) - 于v2.3停止更新 @bad-apple-world-preset gastronomy-works@ @create-air-fabric @@ -16718,7 +16718,7 @@ tokusatsurecord|特摄唱片 (Tokusatsu Record) origins-backgrounds-forge¨origins-backgrounds cataclysm-apotheosis-addon -cave-dweller-reimagined¨cave-dweller-reimagined-fabric +cave-dweller-reimagined@cave-dweller-reimagined-(fabric)¨cave-dweller-reimagined-fabric out-of-combat|脱战 (Out of Combat) soulfragment|灵魂碎片 (Soul Fragment) @@ -16734,7 +16734,7 @@ its-magispelling-time @unkindled recipe-generator@ @carbonfltr -argonmod@|氩 (Argon) +argonmod|氩 (Argon) - 无4.0.2+¨@argonmod|氩 (Argon) foggypalegarden@ @items-stack arkane-domains @@ -16749,7 +16749,7 @@ petphrasex@|口癖X (PetPhraseX) dust-and-ash|尘与烬 (Dust and Ash) @particle-interactions @trial-spawner-timer|试炼刷怪笼计时器 (Trial Spawner Timer) - +enigmatic-delicacy|神秘佳肴* slashblade-resharped-renderfix-patch@|拔刀剑:重锋 渲染修复补丁 (SlashBlade Resharped RenderFix Patch) @invulnerable-wheat skin3d-but-not-cubic@skins3d-but-not-cubic @@ -16811,7 +16811,7 @@ game-discs@|游戏光碟* luncheon-meat-s-delight@|午餐肉乐事 (Luncheon Meat 'S Delight) vanilla-plus-resourceful-tools¨resource-tools-fabric -create-horse-power@ +create-horse-power@|机械动力:马力* ars-technica plustic-reforged@ spawners-plus@spawners_plus|刷怪笼拓展 (Spawners+) @@ -16997,7 +16997,7 @@ scroll-tweaks@scrolltweaks|鼠标滚轮调整* st@server-tools|服务器工具 (Server Tools) ride-every-thing@|骑乘一切* cit-resewn-neopatcher@ -legendary-creatures|传说生物* +legendary-creatures@|传说生物* @meowcraft-mod @@ -17063,7 +17063,7 @@ scp-ultimate herobrines-omen@herobrinesomen sws-wukong dragns-valiant-vehicles@dragns-valiant-vehicles! - +bio-factory@biofactory create-ad-astra-compatibility create-mechanical-chicken@ zbgt@ @@ -17093,7 +17093,7 @@ sons-of-sins-organs-additions|七罪之子:器官添加 (Sons Of Sins: Organs sons-of-sins-organs-museum|七罪之子:器官博物馆 (Sons of Sins: Organ Museum) mobs-of-sins-alexs-mobs-integration-forge|七罪之子: Alex的生物集成 (Mobs of Sins: Alex's Mobs Integration [Forge]) stitched-sins|缝合的罪* - +@bio-delight the-sculpture@scp-173,-the-sculpture|SCP-173,雕像 (SCP-173,The Sculpture) goop@ @@ -17142,7 +17142,7 @@ unilib@ modchu_playerformlittlemaidfml quiet|迁噪碍音/玩家禁言* -galacticraft-compatibility@|星系兼容* +galacticraftcompatibility@|星系兼容 (Galacticraft Compatibility) @bettertab modular-machinery-reborn-mekanism@ @@ -17277,8 +17277,8 @@ tfcgenviewer@ terra-math@ lukis-crazy-chambers tumbleweed-wasteland -irons-jewelry|Iron的宝石与饰品 (Iron's Gems 'n Jewelry) -atlas-api +irons-jewelry@|Iron的宝石与饰品 (Iron's Gems 'n Jewelry) +atlas-api@ loading-screen-messages @pipesounds nemos-invertory-sorting@nemos-inventory-sorting @@ -17294,7 +17294,7 @@ centrifuge-tiers-reproduced@ dense-mekanism@ simple-missles@ galosphere-spellbooks@ -discerning-the-eldritch|窥玄探秘* +discerning-the-eldritch@|窥玄探秘* @hexcassettes wardens-plus ender-wyrmlings @@ -17429,7 +17429,7 @@ enchantmentransfer@|附魔转移 (Enchantment Transfer) @async @mtr-ante|无名的纸板箱交通拓展 (Aphrodite's Nemo's Transit Expansion) -touchcontroller@|触摸控制器 (TouchController) +touchcontroller|触摸控制器 (TouchController) - 文件暂时不会及时更新¨@touchcontroller|触摸控制器 (TouchController) let-me-click-and-send@ @@ -17513,7 +17513,7 @@ water-particles-render-fix ray-trace-library delayed-thunder@ book-and-quick-save@ -keybaordjs@keyboardjs +keybaordjs¨@keyboardjs|Keyboard JS - 已弃用 better-eating-mod@better-eating @heart-effects @@ -17543,7 +17543,7 @@ canned@ mushroooms@ tacz-compatibility @baablu-natural-disasters -@tacz-tweaks +tacz-tweaks@ un-saddle @item-landing-sound rehooked@ @@ -17627,7 +17627,7 @@ portable-craft-bench portable-tables@ptables @flux-network-fork-neoforge resource-backpacks@ -etherology@ +etherology@|以太学* snackpirates-aeromancy-additions@aeromancy-additions apothic-enchanting@|神化附魔* maidsoul-kitchen@|女仆厨房* @@ -17745,7 +17745,7 @@ the-twilight-forest-unofficial@|暮色森林非官方版* somnia-awoken-gui-beautify|梦醒时分 - GUI美化 (Somnia Awoken - GUI Beautify) redstonecraft@redstonecrafts|红石工艺 (RedStoneCraft) mowzies-mobs-baubles|Mowzie 的生物饰品 (Mowzie's Mobs' Baubles) - +@teblslashblades|拔刀剑:最后战线附属包 (SlashBlade: The End Battle Line Addon) dungeons-and-combat @blossomlib @@ -17773,7 +17773,7 @@ checklist puncher@ -sensible-stackables +sensible-stackables@ rei-addons-ic2classic @blossomtpa @lithium-raycast-fix @@ -17921,7 +17921,7 @@ plumbob @more-enchantments-soybeani-more-enchantments|豆浆更多附魔 (More Enchantments by Soybeani) -@clearviews +clearviews@ @immersive_weapons_sounds epic-fight-invincible@epic-fight-invincible-lib|无坚不摧 (Epic Fight - Invincible) ore-dict-re-order @@ -17961,7 +17961,7 @@ cosy-critters@ omnilib@ astages @aloria-loading -@parentalcontrols +@parentalcontrols|家长控制 (Parental Controls) jason-bot|Jason机器人* fly-in-the-sky|高空飞行 (Fly in the sky) @@ -18174,7 +18174,7 @@ stacksonstacks more-of-all@ -chiikawa|吉伊卡哇* +chiikawa@|吉伊卡哇* tolkien-tweaks tolkien-tweaks-mobs-edition soulplied-energistics @@ -18265,7 +18265,7 @@ extended-tinker@|匠魂扩张* bedsheet@|床单 (BedSheet) despawn-tweaks@ planetguylib -@fabric-kotlin-extensions +ethans-kotlin-extensions@ crafting-table-plus@ @replay-export @loot4everyone @@ -18295,7 +18295,7 @@ retro-sophisticated-backpacks|复古精妙背包* grid@ emienchants@ random-complement|随意补充 (RandomComplement) -thedulling +the-dulling batsword unidye@ obs-overlay@ @@ -18395,7 +18395,7 @@ asyncparticles@|异步粒子 (AsyncParticles) @dragonfly-model-library @terrainapi mnmutils -betterend-elytra-fix@ +betterend-elytra-fix@|更好的末地鞘翅修复 (BetterEnd Elytra Fix) player-swap @configurable-everything no-hearts-ui@ @@ -18519,7 +18519,7 @@ tinkers-advanced@|前沿匠艺系列 (Tinkers' Advanced) - 前沿匠艺-拆分 more-structure-processors@ quick-stack-to-nearby-chests@quickstack|快速堆叠 (QuickStack / Quick Stack To Nearby Chests) -@kallfix +@kallfix|卡了 OK (KAllFix) yggdrasilproxy@yggdrasil-proxy thaumic-meme|神秘吐槽* @@ -18765,7 +18765,7 @@ valkyrien-relogs@ @section-31|简单监控 (Section 31) chatalert aaa-particles-world@ -pick-up-tnt@ +pick-up-tnt¨@pick-up-tnt|Pick Up TNT - 不再更新 ymadditions@ immersive-machinery@ xp-synthesiser@ @@ -18809,7 +18809,7 @@ hxchud sorted-enchantments@|排序附魔* assassins-creed-parkour-mod|刺客信条跑酷 (Assassin's Creed Parkour Mod) critter-armory@ -the-harvest +the-harvest@ better-minoshroomtaur|Better 米诺菇* moobloom|哞菇与哞花 (Mooblooms and Mooshrooms) mooblooms-and-mooshrooms-bop-edition|哞菇与哞花BoP版 (Mooblooms and Mooshrooms (BoP Edition)) @@ -18844,7 +18844,7 @@ rough-blade|无锋道* @appledog|苹狗 (Appledog) superb-warfare@|卓越前线* -aetherworks-extended-life@aetherworks-unofficial-extended-life +aetherworks-extended-life@aetherworks-unofficial-extended-life|天华之作非官方延续版 (Aetherworks Unofficial Extended Life) pomkots-mechs-extension-pack@ @monkey-utils some-more-blocks@ @@ -18912,7 +18912,7 @@ tetra-apotheosis-fix@ ron-timer empty-air-bubbles bundles-throw-potions -ftb-team-bases +ftb-team-bases|FTB 团队基地 (FTB Team Bases) raworesmelting@ dpt@ potion-effect-enchantment @@ -18927,7 +18927,7 @@ apothic-supplementaries-enchanting apothic-amendments-enchanting apothic-mutant-heads apothic-science-moa-stats -ye-gamol-chattels-reborn@ +ye-gamol-chattels-reborn@|伊加墨的财产:重生* mme@my-more-enchantments|我的更多附魔 (My More Enchantments) @@ -19060,7 +19060,7 @@ simply-more@simplymore wetland-whimsy@|奇湿妙想* -@deputy-placement-switch|求你别放了 (NoG8Put) +@deputy-placement-switch|求你别放了 (NoG8Put / Deputy placement switch) ileafcore dirt-tools-and-armor@|泥土工具与盔甲 (Dirt Tools and Armor) thaumic-rings-of-power|神秘众戒* @@ -19078,7 +19078,7 @@ goety-gotta-summon-em-all@goety-gotta-summon-em-all!|诡法的混沌拓展 (Goet aerlune-rpg-2 origin-furs@ grounded-origins-forge@groundedorigins¨grounded-origins-fabric -@sand-and-gravel-are-forbidden-to-fall|禁止下落 (Nofall) +@sand-and-gravel-are-forbidden-to-fall|禁止下落 (Sand and gravel are forbidden to fall) tinkers-tool-leveling-2@|匠魂工具升级2* remodifier-reborn|你别造了!!!* exporbrecall@|经验球召回 (ExpOrbRecall) @@ -19127,7 +19127,7 @@ growmeal-fabric@growmeal-(fabric) katharite-slimes@slimes-of-kathare botania-ceu -magic-from-the-east@ +magic-from-the-east@|东方秘术* blatapi@ @materials-lib cavelib@ @@ -19191,7 +19191,7 @@ cloud-revive@cloud_revive|云生* paranoia|偏执狂* thaumic-brewing|神秘酿造学* aether-treasure-reforging@ -animations@ +animations@animatium-legacy @pet-necropolis paddle-boats true-darkness-elementriy @@ -19260,7 +19260,7 @@ moves-like-mafuyu@|高速企鹅* vanillin@flw-vanillin chain-mining-reforged@chainminingreforged|连锁采集重铸* hrtffix@|HRTF修复 (HRTF Fix) -iammusicplayer-renewed@iam-music-player-renewed +iammusicplayer-renewed@iam-music-player-renewed|Iam 音乐播放器:重生 (Iam Music Player Renewed) misc-tweaks@misctweaks ftb-filter-system neonium@ @@ -19335,7 +19335,7 @@ spitting-image@ mob-journal@|生物图鉴* sdm-shop@ -economy-coins-bills-and-gems +economy-coins-bills-and-gems@economy-coins%2C-bills-and-gems!|经济:硬币、纸币和宝石! (Economy: Coins, Bills and Gems!) multi-hotbar-core multi-hotbar|多重快捷栏 (Multi-Hotbar) properties-panel@propertiespanel|属性面板* @@ -19370,14 +19370,14 @@ flight-affinity@ emcgadgets @minecraft-engineer-edition -@eyelib +eyelib@ custom-armor-bar@ roughly-enough-input-methods@ pirates-doom@ techrebornjei@|科技复兴JEI (TechRebornJEI) little-joys@ blasting-raw-metal-blocks-into-blocks-fabric-forge@blasting-raw-metal-blocks-into-blocks - +pbj-modern-conflict-rearmed @re_mob-control-wands|重制:怪物控制魔杖 (Re: Mob Control Wands) nekoration-reborn|猫咪装饰:重生 (Nekoration-Reborn) @@ -19416,7 +19416,7 @@ gputape@ @limitless-anvil|铁砧限制修复 (Limitless Anvil) carry-on-extend@|搬运:拓展* kaleidoscope-doll@|森罗物语:玩偶* -kamen-tinker|蓝钢匠艺/假面匠艺* + agritech-automated-crops@ @pots-idea|脑洞瓦工 (Pot's Idea) kogtyv-tav@|Kogtyv 的城镇与村庄 (kogtyv-Towny and Village) @@ -19582,12 +19582,12 @@ embers-underground-rooms@ dontstarve4@|不要饿死 4 (Don't Starve 4) spark-core@|星火核心* nether-core-useful-nether-rock|下界核心 (Nether Core) -slashblade-js|拔刀剑 JS (SlashBlade JS) +slashblade-js@|拔刀剑 JS (SlashBlade JS) caerula-arbor@|方块与深蓝之树* alices-doll|元歌 (Alice's Doll) bedrock-redux@|基岩再现* item-entity-control|掉落物清理* -wishing-fountain|许愿泉* +wishing-fountain@|许愿泉* @unbelievable-enchantment|令人不可置信的附魔 (Unbelievable) @hgws-spyglass-a|Hgw的望远镜 (Hgw's Spyglasses) we-do-copyright@ @@ -19643,7 +19643,7 @@ sheep-variety@ @tetraclip|易验锻造 (TetraClip) @rozhud the-cosmic-spokespersons@|宇宙代言者* -mc-mods-in-zero@all-in-zero|一切于零 (All In Zero) +mc-modsinzero@all-in-zero|一切于零 (Allinzero) yunzhu-transit-extension@|云竹交通拓展* goodbye-dirt-screen@|澄清素视 / 去你的泥土屏幕* @@ -19679,7 +19679,7 @@ cybersus@ zettai-rpg@ md-units@md-unit-robots extreme-core -dreamless-spells-and-spellbooks +dreamless-spells gregtech-meowmelmuku|格雷科技非官方版 - 喵呜酱版 (GregTech - MeowmelMuku) @namedloot @enchantment-decay @@ -19736,7 +19736,7 @@ ships-legacy traders-backport banner-patterns-backport@ convenient-effects@ -baguettelib +baguettelib@ fast-event@fastevent @citreforged better-meteorite|更好的陨石* @@ -19950,11 +19950,11 @@ yuns-weather@ qliphoth-awakening@|逆卡巴拉:觉醒* -universal-mod-localizer@universal_mod_localizer|通用模组汉化器* +universal-mod-localizer|通用模组汉化器* - 不再更新¨@universal_mod_localizer|通用模组汉化器 (Universal Mod Localizer) smart-creeper@|智慧苦力怕* peaceful-depletion@peaceful_depletion|和平消耗* @everything-is-tnt -structure-pool-api@ +structure-pool-api@|结构池API (Structure Pool API) create-bluemap@ tragicmc-2-continuation|悲惨世界2:延续 (TragicMC 2 Continuation) nebula-parasites@ @@ -20038,13 +20038,13 @@ mc-modsoy-smelter@alloy-smelter time-in-a-bottle-charger@ just-a-sculk-vial@ lockylocks@ -server-i18n-api@ +server-i18n-api¨@server-i18n-api|Server I18n API - 不再更新 fragmentum-forge@fragmentum¨fragmentum-neoforge¨fragmentum-fabric @monument-data-fix|Monument 数据修复 (Monument Data Fix) @better_tp waystone-currencys@|消币则至 / 传送石碑:货币 (Waystone: Currencys) @armor-stand|盔甲架 (ArmorStand) -threadtweak-reforged|ThreadTweak (Neo)Forge 移植版 (ThreadTweak Reforged) +threadtweak-reforged @environmental-creepers-backport com-bat touhou-little-maid-rpg-class-task@touhou-little-maid-rpg-task @@ -20083,7 +20083,7 @@ eiramoticons horse-breeding-fix@ tablistextension@|TAB 列表扩展 (TabListExtension) rally-of-the-guard-guardvillagers -bncci +bncci@ @recipe-maker portaltransform@|传送门嬗变 (PortalTransform) @a555sd|更多的画 (More Paintings) @@ -20095,7 +20095,7 @@ mekanism-more-machine@mekanismmoremachine|通用机械:更多机器 (Mekanism: custom-battleroyale@|自定义大逃杀 (Custom BattleRoyale) improved-pillager-outpost rebalanced-enchanting@ -dark-doppelganger@ +dark-doppelganger@|黑暗二重身* enders-spellsandstuff hazen-n-stuff@ spongeforge@ @@ -20105,7 +20105,7 @@ sdm-market@ omnilook@ survivalist-essentials@ -villager-trading-plus@trading+ +villager-trading-plus@trading+|简单村民交易+* mending-fix|经验修补修复* colorwheel@ @@ -20285,7 +20285,7 @@ enchantment-to-attack-faster|并非环取/攻击速度附魔非官方版 (Attack @stopgrowth|停止生长 (StopGrowth) squid-no-glitch @permanent-night-vision|无痕夜视 (Permanent Night Vision) -@rems-carpet-addition +rems-carpet-addition@ colorful-hex@ @@ -20297,8 +20297,8 @@ jei-integration-reborn@|JEI扩展:重生 (JEI Integration: Reborn) appliedsync jei-uu-assembler@ ominous-overlays -create-nuclear-shell@ -world-of-wonders +create-nuclear-shell@|机械动力:核弹 (Create: Nuclear Shell) +world-of-wonders@world-of-wonders-reforged horrible-meteor-shower|陨石雨* desertification@ cinchs-abandoned-houses@ @@ -20527,7 +20527,7 @@ potionsplus tinkers-wood@ illuminated@ galosphere-argent-edge -brutality-a-terramity-addon +brutality-a-terramity-addon@ free-epic-games@|FE 游戏框架 (FreeEpicGames) fabric-database-h2@|H2数据库* @@ -20563,9 +20563,9 @@ create-fantasizing-again@|机械动力:又在幻想了 (Create: Fantasizing Ag crafting-station-jei-edition-updated@ uncraft-everything@ @videoplayer -net-music-play-list@|网络音乐机:播放列表 (NetMusic: Play List) +net-music-play-list@|网络音乐机:更好的体验 (Net Music: Better Experience) -gregs-modern-construct +gregs-modern-construct@ smart-key-prompts@|灵活按键提示 (SmartKeyPrompts) @zero-whitelist|Zero白名单 (Zero Whitelist) @@ -20597,7 +20597,7 @@ neoenchant enhancedtooltips@ locate-fixer journeymap-fixes -ancient-structure@ancient-structures +ancient-cultural-structures@ancient-structures wizard-samurai-irons-spells-addon¨@wizard-samurai-irons-spells-addon|Wizard Samurai - Iron's Spells Addon - 更新较慢 survival-instinct @@ -20692,14 +20692,14 @@ better-questing-immersive-engineering-multiblock bloodyquests croaker@ crawl-on-demand@ -gaze-a-malum-addon +gaze-a-malum-addon|凝瞳 (Gaze) osce|矿石菌种社区版 (OreSpawn Community Edition) explosive-arrow more-relics@ no-sun@no-sun-mod|暗无天日* opposing-force@|针锋相对* -@pingfix +fix-my-pinggggg@pingfix @volumescroll talk-balloons@ health-indicator-txf@ @@ -20803,7 +20803,7 @@ creeperhost-presents-steves-carts@ perspatium@per-spatium offlawn-legacy -metalborn +metalborn|金属之子* @wizards-mod mo-creatures-nostalgia-edition@ mocreatures-awakened @@ -20815,7 +20815,7 @@ treetap@ better-cave-update @affix starry-end -dynamic-trees-for-cobblemon +dynamic-trees-for-cobblemon@ immersive-portal-gun-for-forge@ adorable-hamster-pets@ infinite-lava@|无限熔岩* @@ -20823,7 +20823,7 @@ ethereal-shots|无限箭制 (Unlimited Arrow Works) epic-fight-super-golem|超级铁傀儡 (Epic Fight - Super Golem) fallen-log-hideout -pastel@pastel-mod|彩绘世界* +pastel@|彩绘世界* mob-catcher-unofficial retro-pet-items confluence-dimension-patch|汇流来世维度补丁* @@ -20941,8 +20941,8 @@ fires-ender-expansion thetitansneo@|泰坦生物Neo (TheTitansNeo) vectorientation-reneoforge copyid@|复制ID (CopyID) -@a-lightcolor-beacon -@surveyor +a-light-color-beacon@a-lightcolor-beacon +surveyor-map-framework@surveyor @create-portable-packaging create-more-pipe-bombs-in-packages@ create-bugfix-schematic-patch@|机械动力:蓝图补丁 (Create Bugfix: Schematic Patch) @@ -20964,7 +20964,7 @@ tiger-shark@tigershark|海虎 (TigerShark) better-end-cities-better-end@better-end-cities-for-betterend|更好的末地城 (Better End Cities)¨better-end-cities-vanilla@better-end-cities-base|更好的末地城 (Better End Cities) fallen-gems-affixes -skill-tree +skill-tree@ blatium@ trimica@ fantasy-weapons-medieval-series@fantasy_weapons @@ -21011,7 +21011,7 @@ legends-forge-series-vikings-n-northerners tinkers-battle-spades @its-not-real ly-meteors -ashvehicle +ashvehicle@ @smart-absorption @mooshees city-of-chambers @@ -21273,7 +21273,7 @@ ftb-materials the-great-below illagerblabber@ -serene-season-extended-fix@serene-seasons-plus +serene-seasons-plus@¨serene-season-extended-fix create-enchantment-industry-plus@ boiled-one-unleashed@ creaturecraft-plus @@ -21390,7 +21390,7 @@ aggro-fix@ chest-curio-items@|箱子里的小饰品* galaxysimagination|Galaxy的奇思妙想 (Galaxy's Imagination) delightful-cuisine-for-woodheads-reforged|懒人厨房·重制 (Delightful Cuisine for Woodheads Reforged) -@loans-cinema +loans-cinema@ banana-baking@ duacan-delight|岩城乐事* slashblade-refabricated@|拔刀剑:重织 (SlashBlade: Refabricated) @@ -21402,7 +21402,7 @@ dirtcraft-unoffical|泥土工艺 - 非官方版 (DirtCraft - Unofficial) specialmaterials@|材料 (Materials) @onlytpnext|仅传送Next (OnlyTPNext) easy-survival-command|简单生存指令TP (EasySurvivalCommand) -@gensokyo-reverie-of-lost-dreams|梦隐的幻想乡 (Gensokyo Reverie of Lost Dream) +gensokyo-reverie-of-lost-dreams@|梦隐的幻想乡 (Gensokyo Reverie of Lost Dream) reliquified-ars-nouveau@ requesting-interface@irequesting applied-experienced@ @@ -21449,13 +21449,13 @@ inhrtburys-coin|Inhrtbury的金币 (Inhrtbury's Coin) @firesight epic-villages @brainfk-in-minecraft -@aviator-dreams-reloaded +aviator-dreams-reloaded@ russian-dream -cbc-ballistics +cbc-ballistics|机械动力火炮:弹道学 (CBC Ballistics) create-programmable-pals create-ore-excavation-plus@ -@createtreadmill|机械动力:跑步机 (Create:Treadmill) +create-treadmill@createtreadmill|机械动力:跑步机 (Create: Treadmill) create-more-package-couriers@ regius@ @@ -21464,7 +21464,7 @@ regius@ dont-make-me-see-red|别让我红温* peyros-scythe|佩伊洛的镰刀 (Peyro's Scythe) - +@kamenriderex-aid|假面骑士:艾克赛德 (Kamen Rider EX-AID) @readstar|占星术 (ReadStar) illage-and-legend giant-dead-oak-tree@ @@ -21492,7 +21492,7 @@ countryball@minecraft-countryball-mod tinkers-construct-emergence pointblank-jelly@pb-jelly terrabnormals -extra-mining-world +extra-mining-world@ herobrine011840s-nether-biomes my-end update-ender-a-reimagine-of-the-end @@ -21623,7 +21623,7 @@ audaki-cart-engine@ @force-beacon-load|信标强制加载 (Force Beacon Load) humansmod@humans-mod -aquaacrobaticslegacy +aquaacrobaticslegacy@aqua-acrobatics-legacy-(ragecraft-version)|水游技艺:传承 (Aqua Acrobatics Legacy) @modmenu-btw armored-arms@ armored-golems @@ -21700,7 +21700,7 @@ winefoxs-spellbooks@winefoxs_spellbooks|酒狐的魔法书 (Winefox's Spellbooks better-allay@ codechicken-lib-cre@ solcarrot-fix|生活调味料:胡萝卜版 - 死吃修复* - +roachs-delight-farmers-misery|蟑螂乐事/农夫悲事 (Roach's Delight/FarmerMisery) nanexs-cherry-biome digital-botania @@ -21909,7 +21909,7 @@ no-template-netherite@ @mobcapture koifish@ - +@mindshaft @mcinspects @csgo-inspects armor-hud-by-mcjunky33@ @@ -22062,7 +22062,7 @@ amnesia-core @minecraft-sqlite-jdbc sqlite-jdbc forbidden-arcanus-js - +@recipe-helper|配方编辑器 (RecipeHelper) @sha1redemption f-k-create-world@fcw|不要创建世界 (F××k Create World) maid-task-scrolling-helper@|女仆任务滚动助手* @@ -22253,7 +22253,7 @@ gtbcs-geomancy-plus @hiirosakura -us-universal-summoning-new|通用召唤 (Universal Summoning) +us-universal-summoning-new|通用召唤 (Universal Summoning/Universal Summoning New) @gambac cad-editor@ @@ -22304,8 +22304,8 @@ makeae2better@ @past-el-palettes @blowing-bubbles mobs-n-growths-origins -roadarchitect-encounters - +roadarchitect-encounters@ +@extended-drawers-polymer @mystcraft-ageless thaumcraft-return-of-the-ancients@ @@ -22355,7 +22355,7 @@ awakened-craziness villager-market-nbt-fix|村民市场 - NBT 修复版 (Villager Market - NBT Fix) illager-expansions @tropicraft-stationapi -bbl-shops +bbl-shops@ @dronebox @volo-volare @customanvilrenameable|自定义铁砧改名权 (CustomAnvilRenameable) @@ -22372,7 +22372,7 @@ recipes-of-the-lost@|失落配方 (Recipes of the Lost) tacz-pack-upgrader@ -@ping-view +ping-view@ extravagant-delight@ @@ -22395,7 +22395,7 @@ mytown @linverticalslab|竖半砖 (Lin Vertical Slab) @pearlchunkloader|珍珠区块加载器 (PearlChunkLoader) -@lv-wings +lv-wings4@lv-wings @foxbox @more_than_a_foxbox @fox-plushies @@ -22624,7 +22624,7 @@ yumo-isk-stackstackstack|拼好组 (Yumo isk-Stackstackstack) flourfood|面食 (Flour Food) -neo-ae2-toogleable-view-cell@|AE2可切换显示原件-Neo (Neo AE2 Toogleable View Cell) +neo-ae2-toogleable-view-cell@|AE2可切换显示元件-Neo (Neo AE2 Toogleable View Cell) mtt-more-tetra-tools-new|更多Tetra工具 (More Tetra Tools) compat-delight@ @@ -22653,7 +22653,7 @@ maid-travel-camera|女仆旅行相机* nieih@ origins-furs-updated@ -tinkers-gears@|匠械 (Tinkers' Gears) +tinkers-gears|匠械 (Tinkers' Gears) - TGears V1,¨tinkers-gears-2|匠械 (Tinkers' Gears) - TGears V2¨@tinkers-gears|匠械 (Tinkers' Gears) moreeverything epic-fight-skill-tree@ @@ -22691,7 +22691,7 @@ clanging-howl limbus-company-mod-lcm|边狱公司:麦块部门 (Limbus Company Mine Section) realistic-bird-behavior@|真实鸟类行为* -wing-kirin|翼麒麟* +wing-kirin@wing-kirin-dragon-survival-addon|翼麒麟* mcheli-reforged-mch-r @@ -22783,7 +22783,7 @@ parcoolskill@|跑酷技能 (ParCoolSkill) merchantjs@ fetchjs@ mc-mods-the-mafuyu@all-the-mafuyu|万象瑞雪 (All the Mafuyu) -goety-end-biomes +goety-event-horizon@ structure-spawn-area-fix@|结构生成区域修复* aloof-new@aloof¨aloof|Aloof! - 旧版 @@ -22836,7 +22836,7 @@ display-edit arthys-elemental-engine-aee-forge bobby-legacy@ ambience-mini -arthys-ebonite-ae-forge +arthys-ebonite-ae-forge@arthys-ebonite-(ae) enigmatic-arcana mekanismceu-moremachine|通用机械:更多机械 (Mekanism-CE-Unofficial-MoreMachine) optifine-cit-enchantment-patch@optifine-cit-patch @@ -23124,7 +23124,7 @@ industrial-revolution-by-redstone-rebooted omoshiroi-kamo death-taxes@death-and-taxes -@emotional-support-creeper +emotional-support-creeper@ ly-ender-compass walking-crops harken-scythe-resharpened@ @@ -23151,7 +23151,7 @@ dyson-cube-project|戴森立方计划* annoying-villagers-herobrine-invasion@|烦人的村民 (Annoying Villagers: Herobrine Invasion) @net-music-peripheral|网络音乐机:电脑外设 (Net Music: Peripheral) - +@simpletranslation|简单翻译 (SimpleTranslation) tacz-golem-compat@|傀儡装配-战术人形 (TaCZ Golem Compat) customizable-player-health@|可自定义的玩家生命值* @@ -23316,7 +23316,7 @@ fastconfigapi@ @awakeapi @anti-offhand @so-tuff - +lootswap@ moreeventsjs spudiversity-more-and-more-potato|土豆盛宴-更多的土豆! (Spudiversity - More And More Potato) @@ -23383,7 +23383,7 @@ anothertips@ copper-drops cooks-collection@ - +bosses-unleashed no-end|我的末地呢?!* ome-tweaks@ clutter-no-more@ @@ -23518,7 +23518,7 @@ craycrafting @rain-world-weaponry customtitlescreen unusual-prehistory-2@|不寻常的史前史2* -shield-api@ +shield-api@|盾牌 API (Shield API) brick-lib-api@ whatversioning|申必版本号 (WhatVersioning) yourender|完了 (YourEnder) @@ -23657,7 +23657,7 @@ northstar-redux@|机械动力:北极星 - 重制版 (Create: Northstar - Redux create-igneodynamics@ create-numismatics-utils@ create-vintage-unofficial-port@create-vintage-(unofficial-port) -create-bits-n-bobs@ +create-bits-n-bobs@|零零件件 (Create: Bits 'n' Bobs) ccb-tweaks@ccbt gregtechceu-modern-extra-unofficial@gtceu_extra geotetraarmor @@ -23685,7 +23685,7 @@ mossylib@ better-trading-menu@|更好的交易菜单* easy-npc-config-ui@ evolved-mekanism-extras|通用机械:进化扩展* -@music-hud +music-hud@ backups inventory-particles@|物品栏粒子* backport-recipes@ @@ -23749,7 +23749,7 @@ tacz-turrets@ health-scoring supearlposition daisy@ -no-fall-mod +no-fall-mod@no-fall-damage @farmers-assortment foggy-world @@ -23759,7 +23759,7 @@ techrot@ create-easyfilling@ mortar-and-pastels@mortar @delicate_little_garbage|精致小垃圾 (Delicate Little Garbage) -elementalcraft-reactions@|属性锻造:元素反应 (ElementalCraft: Reactions) +elementalcraft-reactions@|属性锻造:元素反应 (Elemental : Reactions) kubejs-recipes-create|KubeJS配方生成 (KubeJS Recipes Create) hamstercore@|仓鼠核心 (HamsterCore) @@ -23818,7 +23818,7 @@ spinning-items-displayer-mod@ attack-range-attribute@ kreno-fpatcher@ riautomobility@ -cyberware-reforged@ +cyberware-reforged@|机械改造:重生 (Cyberware: Reforged) project-plastic blocky-file@|方块文件* @@ -23830,7 +23830,7 @@ nocubes-food-desire prehistoric-rift@ -more-slashblade-ex-enchantment-effects|超多附魔增效 (More SlashBlade Ex Enchantment Effects) +more-slashblade-ex-enchantment-effects@|超多附魔增效 (More SlashBlade Ex Enchantment Effects) tinkers-emc|匠魂EMC (Tinker's EMC) @tacz-advanced-deathinfo|TACZ 高级死亡信息 (TACZ Advanced DeathInfo (Kill Banners)) summon-totem|召唤图腾* @@ -23867,7 +23867,7 @@ i-f-amphithere-mod unu-civilian-pack-for-mts@unu-civilian-pack rlcraft-manhunt -ancient-forgemastery@ +ancient-forgemastery@|远古锻造* relentless-undead @classic-farlands|经典边境之地 (Classic Farlands) @@ -23890,7 +23890,7 @@ one-enough-enchantment@ one-enough-value@ @coordinate-manager|坐标管理器 (Coordinate Manager) - +@legacy-fabric-api-fixes-btw @gravestones-fix|墓碑修复 (Gravestones Fix) whimsy-deco@ incendium-translation-fix@ @@ -23958,7 +23958,7 @@ sync-forge @sync-fabric-reported player-shells@|玩家躯壳* cataclysm-ametyst-crab-temple@ -@entity-0-elytra-fix +entity-0-elytra-fix@ goety-mastery-of-magic-addon@|巫法精通 (Goety: Mastery of Magic Addon) malisis-doors-revived akuto-engine @@ -24013,7 +24013,7 @@ thebladeofchaos49|混乱处刑 (Blade of Chaos) maophone-trial-version@|MaoPhone体验版 (MaoPhone - Trial Version) -mrmc|世界之心 (Aether Cores) +mrmc@|世界之心 (Aether Cores) @arcane-vortex|秘法涡流 (Arcane Vortex) @@ -24049,7 +24049,7 @@ hud-control@|HUD 控制 (HUD Control) mchce|MC 直升机:社区版 (McHeli: Community Edition) -easier-proud-soul-acquisition|更容易的耀魂碎片获取 (easier proud soul acquisition) +easier-proud-soul-acquisition@|更容易的耀魂碎片获取 (easier proud soul acquisition) @slashblade-re-refabricated|拔刀剑:再重织 (SlashBlade: RE-Refabricated) mystical-agriculture-community-edition@|神秘农业社区版* lazydfu-reloaded@ @@ -24060,7 +24060,7 @@ easydoll@|简易玩偶 (EasyDoll) gaiatweaks@ luckier-clover@|更幸运的四叶草* -create-electro-energetics|机械动力:电力学 (Create: Electro Energetics) +create-electro-energetics@qYJdIoAx|机械动力:电力学 (Create: Electro Energetics) universal-trinkets@ roguelike-tower|肉鸽塔 (Roguelike tower) @@ -24071,7 +24071,7 @@ prims-stranger-things-mod super-backpack@|超级背包* - +ntm-cursed-addon-edition radiance¨@radiance-mod-windows¨@radiance-mod-linux xaerolib@ @@ -24095,7 +24095,7 @@ no-fly-zone@ twod-projectiles@twod_projectiles|2D 投掷物 ➵ (2D Projectiles ➵) morethermalevaporation autofish-for-tic@|自动钓鱼Forge版-匠魂兼容 (AutoFish for Tic) -wtc-wizard-terra-curios-new|泰拉饰品拓展 (Wizard Terra Curios/Wizard Terra Curios New) +wtc-wizard-terra-curios-new|泰拉饰品拓展/泰拉灾厄饰品 (Wizard Terra Curios/Wizard Terra Curios New/Calamity Terra Curios) invitemmark@|物品标记 (InvItemMark) book-of-dragons@ @@ -24287,7 +24287,7 @@ debugify-reforged ae2-tangible-bookmarks@ deadbyskillcheck@ stormies-spiders@ -saints-dragons@ +saints-dragons@|Saint的群龙 (Saint's Dragons) buildtech|建筑科技 (BuildTech) @@ -24302,7 +24302,7 @@ buddy-drones@ shels-minechat legacy-gamemode-switcher @borukva-food-exotic -betterblockz@ +betterblockz@|更好的方块 (BetterBlockZ) packagedtacz|封包TaCZ (PackagedTacz) lotas-light@ @@ -24311,7 +24311,7 @@ mob-locator@ zoesteria -@random-optimization|随意优化 (Random Optimization) +random-optimization@|随意优化* embergetic-expansion-embers-rekindled irons-spell-books-arcane-essence-blocks@irons-spellbooks-arcane-essence-blocks vampirism-irons-spells-compatibility@ @@ -24532,9 +24532,9 @@ santa-logistics@ easy-survival-plus@easy-survival-mod - +neon-glitch-exiled-divinity|霓闪感染:被放逐的神迹 (Neon Glitch: Exiled Divinity) @kaleidoscope-texture-expansion|森罗厨房:乐此不疲 (Kaleidoscope Texture Expansion) -legendary-additions +legendary-additions@ tacz-arcana-timeless-and-classics-guns mebahels-creatures-draugr@|Mebahel 的生物 - 尸鬼 (Mebahel's Creatures - Draugr Invasion) @@ -24552,7 +24552,7 @@ mace-enchantments masterful-machinery-fork enchantment-limiter-fork cobblemonraiddens@ - +@terrain-diffusion heartmod@binding-of-isaac-heart-mod pokecube-legacy@ seasons-mc @@ -24606,7 +24606,7 @@ instantworldmirror|瞬时世界之镜 (Instant World Mirror) @floating-excavation|浮空挖掘 (FloatingExcavation) hostile-mobs-and-girls-recall|敌对生物与魔物娘:追忆 (Hostile Mobs and Girls: Recall) @holoinventory-reloaded|物品显示/物品清单:重载版 (HoloInventory-Reloaded) -@everlaartifacts|七叶镜绫的随想/言引定制 (EverlaArtifacts) +@everlaartifacts|七叶镜绫的随想 (EverlaArtifacts) @bright-sunflower|曜日金葵 (Bright Sunflower) atm-azure-tetra-materials|蓝钢锻艺 (Azure Tetra Materials) @@ -24675,7 +24675,7 @@ christmascolonies|圣诞殖民地 (ChristmasColonies) mnaoverpowered@ stack-manager@ ae2-mod-id-view-cell -@curios-media-holder|我抄饰你的媒 (Curios Media Holder / Trinkets Media Holder) +@curios-media-holder custommeteorjs@ kaleidoscope-end@|森罗物语:末地* macaws-regions-unexplored@ @@ -24701,7 +24701,7 @@ foolish@ entitychinesename@|实体中文名 (Entity Chinese Name) firma-aircraft@|群峦航线* -exura@|更多实用设备:再重生 (Extra Utilities Reborn Again) +extra-utilities-reborn-again@exura|更多实用设备:再重生*¨exura|更多实用设备:再重生 (Extra Utilities Reborn Again) shaolib@ manage-your-ship@ @@ -24807,7 +24807,7 @@ rats-ratn-edition @eye_of_dreams foolish-unofficial-patch -@damage-engine|伤害引擎 (Damage Engine) +damage-engine@|伤害引擎* travelers-lib@ @@ -24825,7 +24825,7 @@ ae2-tech-add-on|AE2 科技扩展 (AE2 Tech Add-On) @create-nerfad questextra flans-mod-npc-vehicles -@easyloginmod|简易登录 (EasyLogin) +@easyloginmod|简易登录 (EasyLoginMod) inventory-sort-nf|背包整理(NeoForge 版) (Inventory Sort (NeoForge Edition)) someordinarytweaks-forge@ordinarytweaks @@ -24839,9 +24839,9 @@ adventurers-amulets|冒险家的护身符 (Adventurer's Amulets) create-frequency-filter|机械动力:频率过滤器 (Create: Frequency Filter) @accurate-skies - +@antideath-carpet-addition|AD的Carpet拓展 (Antideath Carpet Addition) @xytpa|Xy的TPA模组 - +@hlgid|heef的轻量级游戏信息显示 (Heef's Lightweight Game Information Display) steves-factory-manager-reborn|史蒂夫工厂-重置 (Steve's Factory Manager Reborn) steves-addons|史蒂夫工厂管理附加 (Steve's Addons) ffs-fancy-fluid-storage@|液体存储 (Fancy Fluid Storage) @@ -24885,7 +24885,7 @@ abyssalcraft-remnant-tweaker herobrine-companion@herobrine_companion|Herobrine 的陪伴* industrial-forestry avaritia-lite-extended -goety-hostility@ +goety-hostility@|诡厄敌袭* fish-traps-remastered@|鱼栅非官方重制版* the-ponderer@|思索者 (Ponderer) fallen-lib @@ -24992,7 +24992,7 @@ pulsetech@ @night-lights-polymer snowscaress@ -@new-sim-u-kraft|新:模拟大都市 (New: Sim-U-Kraft) +new-sim-u-kraft@|新:模拟大都市 (New: Sim-U-Kraft) fancy-outlines ae2-pattern-encoding-access-terminal@ efficient-entities@ @@ -25008,7 +25008,7 @@ goslingcord emojicord @logarithmic-volume-control scarcity@ -mo-bends-unofficial-modern-port@|更多弯曲 [现代化移植] (Mo' Bends [MODERN PORT]) +mo-bends-unofficial-modern-port|更多弯曲 [现代化移植] (Mo' Bends [MODERN PORT])¨@mo-bends-unofficial-modern-port|更多弯曲 [现代化移植] (Mo' Bends [MODERN PORT]) - 更新滞后 @supersteve|超级史蒂夫 (Super Steve) longroad|前路已明 (Road Is Ahead) @@ -25035,7 +25035,7 @@ yeti-jar-dweller create-automotives@ exposure-expanded@ smart-spawn@ -random-mob-textures@ +random-entity-textures@ uifadefx@ daily-boss@ it-seems-loosely-connected-to-the-ceiling@|与洞顶的连接似乎并不牢固 (It seems loosely connected to the ceiling) @@ -25099,7 +25099,7 @@ ae2-overclocked@|AE2超频 (AE2 Overclocked) @bred-is-cool integrated-mekanism@ rainjava - +fabric-gui-imgui@ me-soul-card @@ -25119,7 +25119,7 @@ display-tool|生物显血·极轻量版* technological-assistance|科技辅助* not-enough-bandwidth@|网络包优化* apprentices-codex@|学徒法典 (Apprentice's Codex) -asterism-arcanum-addon-for-irons-spells-and +asterism-arcanum-addon-for-irons-spells-and@asterism-arcanum-addon-for-irons-spells-and-spellbooks wizards-help-irons-spells-n-spellbooks-addon ftw-addon new-years-delight-a-farmers-delight-add-on@new-years-delight-(a-farmers-delight-add-on) @@ -25135,7 +25135,7 @@ armor-3d@ hot-iron dangerous@ balaclava-mod@ -@goodblock +@changed_more_creatures tacz-sound-attracts-zombies@|枪鸣尸至 (TaCZ: Sound Attracts Zombies) ntrials@ alexs-mobs-fabric|Alex 的生物(Fabric) (Alex's Mobs (Fabric)) @@ -25178,7 +25178,7 @@ you-are-the-zombie-2-0|我是僵尸 (As The Zombie) mobcivics@ tarotcards-remastered@|塔罗牌:重制 (TarotCards: Remastered) -@copyblock|复制方块 (Copy Block) +copyblock@|复制方块 (Copy Block) @apocalypse_end|天启终焉 (Apocalypse End) @bypass-fabric-check @@ -25190,7 +25190,7 @@ openlink-chmlfrp-extension@|开放式联机:Chmlfrp扩展 (OpenLink Chmlfrp Ex enchantato-enchantment-table -red-envelope-reforge|红包 (Red Envelope) +red-envelope-reforge@|红包 (Red Envelope) advanced-backups-patch@|高级备份补丁* @network-proxy @asmcraft @@ -25238,7 +25238,7 @@ create-fluidlogistic@createfluidlogistic|机械动力:流体包裹 (Create: Fl kubejs-create-automation@ nether-star-items-forge@nether-star-items¨nether-star-items-fabric piglin-hunt-fix -ok-core +ok-core@|OK 核心 (OK Core) @ccexpand-peripherals|CC拓展外设 (CCExpand Peripherals) colored-end-crystals @@ -25351,7 +25351,7 @@ baby-steps vintage-damage-indicators@|复古伤害显示* - +lol-equipment|LOL装备 (LOL Equipment)¨lol-new|LOL装备 (LOL Equipment) apricityui@|晴雪UI (ApricityUI) functionalstoragelegacy@|怀旧功能性存储 (FunctionalStorageLegacy) @@ -25474,15 +25474,15 @@ mekanism-sun|通用机械:人造太阳* anvil-lib@ vkbc|葡园酒香森罗酒馆酒瓶兼容 (Vinery & Kaleidoscope Tavern Bottle Compatibility) -kaleidoscope-world-liquor@|森罗酒馆:世界名酒* +kaleidoscope-world-liquor@|森罗物语:世界名酒* @burning @burning-energy realisticsunburn novums-altitude mystcraft-reborn@|神秘岛:重生* @cod-pattern -tacz-lesraisins-tactical-equipements@lr-tactical|TaCZ:绿葡萄战术装备 (TACZ: LesRaisins Tactical Equipements) -rotten-ruins|腐朽之疫* +tacz-lesraisins-tactical-equipements@lr-tactical|TaCZ:绿葡萄战术工坊 (TACZ: LesRaisins Tactical Equipements) +rotten-ruins|腐朽之疫:腐化 (Rotten Ruins: Corruption) strawgolem-extended-life@straw-golem-unofficial-extended-life|稻草傀儡非官方延续版 (Straw Golem Unofficial Extended Life) caves-not-cliffs-backport@caves-not-cliffs-backported irons-spells-n-spellbooks-scroll-description @@ -25491,7 +25491,7 @@ cnpcextended yifeis-game-adjustments|Yifei的游戏调整 forcegl3-1 baublevault - +universal-draconic-slots|通用龙研格子* embellish-chat@ changed-extra @@ -25531,7 +25531,7 @@ debug-block-overlay-reloaded@debug-block-collision-reloaded @progressive-tiers|阶梯强化 (Progressive Tiers) apothic-enchanting-addition|通量神化附属* puddles-floods@ -star-dragon-dragon-survival-addon|星辰龙 (Star Dragon) +star-dragon-dragon-survival-addon@star-dragon|星辰龙 (Star Dragon) casual-swing @carvedmelon @@ -25621,7 +25621,7 @@ beyond-storage-addon@beyond_storage_addon decapitation @narrow-zone @aekey-legacy -okbackpack +okbackpack|OK背包 (OKBackpack) thirst-was-tweaked@ @loots @@ -25689,7 +25689,7 @@ tlm-attributedisplay|车万女仆:属性显示 (Touhou Little Maid: Attribute jadeownerfix@|一眼认主 (JadeOwnerFix) logistics-network@logisticsnetwork @the-tnt-mod - +stronger-mekasuit|更强的Mekasuit* @elytra-chestplate-swap @noloadingterrain|无加载地形中 (No Loading Terrain) @@ -25719,7 +25719,7 @@ create-paper@|机械动力:纸张* @a-villagers-dream colds-wandering-trader majobroom-kots|魔女的扫帚 - 耕云钓月版 (Majo's Broom KOTS) -@simupack|模拟大都市:筑艺坊 (Simupack) +@simcityexpansion|模拟大都市:筑艺坊 (Simupack) waterwheel-immortal|水车仙人* tf-remastered|暮色创世 (TF Remastered) @@ -25755,7 +25755,7 @@ ore-zombies|矿石僵尸* vaders-ore-spiders-mod|矿石蜘蛛 (Ore Spiders) -crafting-slots@ +porta-crafty@ ddlc-chibis create-additional-lights@ @@ -25867,7 +25867,7 @@ lightspeed-kryo memorycatcher gtceu-terminals@|格雷科技终端 (Gregtech Terminals) -superior-spellcasting +superior-spellcasters irons-patreon-library@irons-lib yttrjei@ strange-trinkets@|奇怪饰品* @@ -25899,8 +25899,8 @@ parkour-and-more ash-of-sin-no-more-too-many-structures|罪业余烬:不再过多结构 (Ash Of Sin: No More Too Many Structures) the-between@ evolutionary-defense-tower@|进化防御塔* -the-zoomify-mod@zoomify! -origins-neoforge@|起源 (NeoForge) (Origins (NeoForge)) +zoomlens@ +origins-neoforge|起源 (NeoForge) (Origins (NeoForge))¨@origins-neoforge|起源 (NeoForge) (Origins (NeoForge)) - 不再更新 lovecraft-boyfriends-3 saddlebag-wolf-bag-pet-backpack-companion-utility@saddlebag rpg-attribute-system@ @@ -25927,14 +25927,14 @@ carry-on-back clone-minerador-clone-miner zombies-eat-animals@ the-lost-city@the-lost-city-by-berezka -much-more-dungeons -patchouli-library@ +much-more-dungeons@ +guide-shelf@ weather-refined@weatherrefind @death-rewind|死亡回溯 (Death Rewind) death-keeper@ quickshot@ powerborne-heroes@ -magic-reborn +magic-reborn@|魔法重生* excessive-utilities@ kimetsucraft-kc curiosities-syndicate@ @@ -25959,9 +25959,9 @@ slave-mark|抗争证明 (Mark of Revolution) headpats|摸摸头* golem-magicka@|傀儡装配-奥法魔像* sword-parry-enchant@parry-enchant -golemera +golemera|傀儡纪元* the-imitator -cyberware-visible-modifications@ +cyberware-visible-modifications¨@cyberware-visible-modifications|Cyberware: Visible Modifications - 已停更 structure-crafter@|构印师匠* pop-up-emotes@ @@ -25971,7 +25971,7 @@ overgeared-spartan loot-sentinel@ legendary-titles@ -weapon-rarity\@epic-rpg-weapon-rarity +weapon-rarity@epic-rpg-rarity-system party-hud@ rpg-class-artifacts@ average-dps@ @@ -26012,7 +26012,7 @@ time-in-a-bottle-fix-tiab-fix@ item-glint@|手持物品描边* @mc-273265_fix sbw-colorized@sbw-colorized! -fading-time +fading-time|时光渐陨* @customnpcs-sortplus @customnpcs-crafting-view survival-monitor@ @@ -26034,7 +26034,7 @@ no-lapis-lazuli@|无青金石* stronger-structures warning@ create-bbq-belt|机械动力:传送带烧烤 (Create: BBQ Belt) -shogi +shogi@ mna-arcane-adornment|奥术饰品 (MnA: Arcane Adornment) @in-game-log-display|游戏内日志显示 (In-game Log Display) kindred-legacy¨pokemonmd-disciples-of-corruption @@ -26076,7 +26076,7 @@ petrescue simply-tents-blacklist@|简易帐篷:黑名单* leashall@ auto-swap@autoswap -@reset-villager-trades +reset-villager-trades@ cbc-enchanced-shells-create-big-cannons|机械动力火炮:增强型弹药 (CBC Enchanced Shells) xnet-additions @@ -26093,7 +26093,7 @@ gabous-libs@ sitoneverything@ epic-tnt-mod@ the-body-boosts@ -f-tech-equipment#project-logo@f-tech-equipment|未来科技:装备 (F-Tech: Equipment) +f-tech-equipment@|未来科技:装备 (F-Tech: Equipment) jujutsu-kaisen-ultimate-zenith terramity-paisleys-summoning@paisleys-summoning @@ -26111,7 +26111,7 @@ envelope@ @windy iron-furnaces-fabric-neoforge|更多熔炉(Fabric & NeoForge) (Iron Furnaces (Fabric & NeoForge)) iron-shulkers|更多潜影盒(Fabric & NeoForge) (Iron Shulkers (Fabric & NeoForge)) -iron-chests-fabric-edition|更多箱子(Fabric 版) (Iron Chests (Fabric Edition)) +iron-chests-fabric-edition|更多箱子:重织 (Iron Chests (Refabricated)) @piggy-lib dungeons-and-villages-deeper-and-darker@ spring-to-life-backport@ @@ -26153,9 +26153,9 @@ simple-snowy-fix-forge-fabric@simple-snowy-fix-(forge-fabric) enchanting-plus-classic universal-portals -oculus-for-simpleclouds +oculus-for-simpleclouds@oculus-for-simple-clouds -@yunbeiuc|云北城建 (Yunbei Urban Construction) +yunbeiuc@|云北城建 (Yunbei Urban Construction) log-deduplicator project-atmosphere@ @@ -26183,8 +26183,8 @@ random-tnt-adds-4-useless-tnt-blocks au-naturel-da-fishing-mod crafter-hologram village-utilities -some-illager-servants -cataclysm-fallingtree-compat +some-illager-servants|诡厄巫法:凶兆 (Goety Ominous) +cataclysm-fallingtree-compat|灾变伐树兼容 (Cataclysm FallingTree Compat) fip-vips-aimoptimizer minepersist@ @@ -26208,7 +26208,7 @@ natures-appetite@|生灵食性 (Nature's Appetite) @misias-manametalmod-extension|米西娅的魔法金属扩展 (Misia's ManaMetalMod Extension) hunger-begone@|饱腹远行* the-boreal@ -kerria +kerria@kerria-opt cc-complementary-core@ cs-towers vista-all-the-cassettes@ @@ -26320,11 +26320,11 @@ tidal-commission|潮汐委托* - data文件为英文¨@tidal-commission|潮汐 @arsenal aaas-strongermobs@strongermobs|更强的怪物 (AAA's StrongerMobs) -@legacy-of-utopia +the-king-and-the-queen-legacy-of-utopia@legacy-of-utopia packcompanion portalmod@ theworldbelow@alpha-below - +@hbm-ntm-high-edition|HBM现代化高版本 (HBM High Edition) mc-matrix-bridge@ @world-importer @@ -26451,7 +26451,7 @@ blocklings-nostalgia-edition|方块酱:怀旧版 (Blocklings: Nostalgia Editio slashblade-amazingshining|荧光惊异 (SlashBlade:AmazingShining) the-god-fox|御神狐之刃 (The God Fox Reborn) -godbirdreborn|毕方重生 (GodBirdReborn) +godbirdreborn|神鸟之刃·棘羽毕方 (GodBirdReborn) balmungreborn|巴鲁蒙格重生 (BalmungReborn) pseudo-edge-dawnbreaker|伪格雷普终境之刃·终境破晓 (Pseudo-Edge: Dawnbreaker) slashblade-thebladechronicles|刀绘卷 (SlashBlade: Egotachi) @@ -26471,7 +26471,7 @@ clutterbestiary@ custom-murder-mystery@|自定义谁是杀手* autotranslator-cn@autotranslator|自动翻译 (Auto Translator) -what-i-wanna-show-u@ +what-i-wanna-show-u madness-mod@madness_entities not-so-shrimple-remolted @peony-from-field-to-table @@ -26561,7 +26561,7 @@ rumors animation-api-reborn attributification@ swgc@ -chunk-animator-embeddium-compat +chunk-animator-embeddium-compat@ tag-fixes@ suns-son-mod @@ -26569,7 +26569,7 @@ disable-villages-generation|禁用村庄生成* locks-reforged@ tidyinv tetra-data-kubejs -@letemburn|让它们燃烧! (Create: LET!EM!BURN!) +let-em-burn@letemburn|让它们燃烧! (LET!EM!BURN!) kaleidoscope-tavern-refabricated@|森罗物语酒馆:重织* kaleidoscope-bloodwine@kaleidoscope_bloodwine|森罗酒馆:血酒 (Kaleidoscope: Bloodwine) @@ -26589,13 +26589,13 @@ mousetweaks-x-accessories-fix@ malice-and-light-cursed-fate-addon-dabura cursed-fate-divine-general-mahoraga create-crafts-additions-and-aeronautics-compat@ -cave-dweller-fabric-port +cave-dweller-fabric-neoforge-port @152mmboom wild-caves-4|洞穴装饰 4* derpy-squid-mod universal-smithing-template|通用升级模版* concatenated-compat -@xaeros-playerposition|Xaero玩家位置显示 (Xaero's Player Position) +xaeros-playerposition@|Xaero玩家位置显示 (Xaero's Player Position) feywild-library grand-depths@grand_depths @@ -26618,7 +26618,7 @@ giant-ice-spike@|巨型冰刺* giant-bamboo@|巨型竹子* quick-boot zfastnoise@ -collision-damage-port +collision-damage-port@ disaster-plants-cursed-fate-addon@disaster-plants-cursed-fate-addon. mrcrayfishs-furniture-mod-tools-refurbished@ hats-x-mo-bends-compat @@ -26632,7 +26632,7 @@ expanded-ecosphere¨@expanded-ecosphere thunderbolt-lib@thunderbolt_lib|闪枢库* crucifix-unbanished country-road-creature@ -ftb-quests-entity-visualization +ftb-quests-entity-visualization@ uncapped-spectator-speed creatures-and-beasts-continued antlers-of-hunger@ @@ -26675,7 +26675,7 @@ nbtech2fix@|超级牛逼的科技整合包2 一些修复 (nbtech2fix) better-safe-bed-backport @sterile-egg|无菌蛋 (Sterile Egg) -okprogressions +okprogressions@ok-progressions ae2enhanced townstead mebahels-api@ @@ -26690,7 +26690,7 @@ starfield-pastoral@|星野牧歌 (StarfieldPastoral) create-radars-unofficial-port@|机械动力:雷达重制版 (Create: Radars Unofficial Port) create-propulsion-simulated@|机械动力:推进工程 - 模拟物理 (Create Propulsion: Simulated) - +easy-create-supplement|简易机械动力* create-additions-sable-compat@ create-stressbound@|机械动力:应力链界 (Create: Stressbound) create-tracks @@ -26700,7 +26700,7 @@ create-dreams-n-desires-updated-to-create-6@ create-more-catalysts@createmore-catalysts|机械动力:更多触媒 (Create:More Catalysts) create-more-alloys create-pipesn-physics@create-pipes-n-phisycs -dabr-dragonsurvivalcompat +dabr-dragonsurvivalcompat@dabr-dragonsurvival-compat block-display@ modern-damage-control@|现代伤害控制* @@ -26730,12 +26730,12 @@ hats-renewed shared-pets@ err422@ kubejsable@ -geckolibbetterfps +geckolibbetterfps@ svdialog@ botania-seeds@ shadow-config@ @nuke-block|核弹 (Nuke block) -projecte-infinite|等价交换无限 (ProjectE Infinite) +projecte-infinite@|等价交换无限 (ProjectE Infinite) modernindustrialization-structure-exporter@mi-structure-exporter|现代工业化结构导出工具 chrono-dawn@ too-much-entities|太多的实体* @@ -26743,7 +26743,7 @@ zero-ordinary-vehicles false-swipe ds-caperspectivefix -create-aeronautics-compatability@ +create-aeronautics-compatability@|机械动力航空学:兼容性 (Create Aeronautics: Compatibility) create-aeroworks@ @flyhigher|直上九霄 (FlyHigher) drive-by-wire-with-sable@drive-by-wire-sable|线缆控制:Sable兼容 (Drive-By-Wire With Sable) @@ -26805,7 +26805,7 @@ illager-structures prehistoric-seacreatures@ - +tlm-cp-chesspvp@tlmcp-chesspvp|车万女仆:棋圣 (Chess PVP) touhou-little-maid-growableores-compat@touhou-little-maidgrowableores-compat|车万女仆:GrowableOres 兼容 (Touhou Little Maid: GrowableOres Compat) plantasia animalsplusplus @@ -26888,7 +26888,7 @@ azumanga-mod@ decofood dragons-reborn the-great-outdoors -riichi-mahjong-forge +riichi-mahjong particle-interactions-reforged inmis-forge-port luminous-monsters@ @@ -26988,7 +26988,7 @@ future-small-decorations|未来小装饰* the-wings-of-fire@wings-of-fire! goetic-legacy@|诡厄遗物* -@1.0.0|诡厄拓展 (Goety:Expansion) +goety-expansion@|诡厄拓展 (Goety:Expansion) an-adaptive-nemesis@anadaptive-nemesis|自适应宿敌 (Adaptive Nemesis) gourmet-boost@|食补* gastro-crisis @@ -27068,7 +27068,7 @@ saverestorer@ camowarfare@|迷彩战车 (CamoWarfare) -dynamic-surroundings-legacy +dynamic-surroundings-expanded|动态环境/动态环绕:扩展版 (Dynamic Surroundings (Expanded)) tacz-recycling@tacz_recycling @the-king-sword @@ -27078,7 +27078,7 @@ quick-access@ harium@ @shared-folder-skins @marketmod -the-summoner-resummoned +the-summoner-reforged stupendous-smeltery-accelerator tiny-potatoes-evolved @@ -27117,7 +27117,7 @@ lootrbiggerchest@|更大的战利品箱 (Lootr Bigger Chest) quark-quilts-n-stains gravity-gun-reforged stupid-things-even-dumber -shreks-revenge +shreks-revenge@ alternative-twilight-remastered-version@alternative-twilight(remastered)|暮色:另类(重置版) (Alternative Twilight Remastere) compact-f3-plus@ @@ -27125,7 +27125,7 @@ flamboyant-repalletized bannerpoint@ mrcrayfishs-vehicle-unofficial@|MrCrayfish 的载具非官方版 (MrCrayfish's Vehicle Unofficial) -@container-indexer-qol|箱柜索引 (Container Indexer) +container-indexer@container-indexer-qol|箱柜索引* armor-set-box|套装盒* have-a-bad-day@ nomorecraters @@ -27190,7 +27190,7 @@ secondwind@second-wind-mod @insomnia ecological-garden@|生态花园* -defenseturrets|防御炮台 (Defense Turrets) +defenseturrets@defense-turrets|防御炮台 (Defense Turrets) @better-furniture-neoforge|更好的家具-NeoForge (Better Furniture-Neoforge) @lamb-lanterns better-storage-lectern@|更好的存储讲台* @@ -27209,7 +27209,7 @@ ice-and-fire-attack-ancient-warfare-2 mythic-tinkers@ @cataclysm-era|灾变纪元 (Cataclysm Era) -@hive-collective|虫巢集群 (Hive Collective‌) +hive-collective@|虫巢集群 (Hive Collective‌) @stacksizetweaks @@ -27225,7 +27225,7 @@ book2map@ simple-music-control@|简单音乐控制* new-visual-keybing@|可视化按键绑定管理* -motley-fins +motley-fins@ rays-of-rament paradox_ mc-so-easy|MC易如反掌 (MC SO EASY) @@ -27235,16 +27235,16 @@ arcadia-ah@ creator-hotbar|创造者快捷栏* ophiopogon-wooden-anvil@|麦冬木砧* @voxyworldgenxaero-bridge -goety-aether|诡厄天境* +goety-aether@|诡厄巫法:天国秘术* immortality-by-adoleiiiiii@|英雄不屈 (Immortality) -neopasterdream|新帕斯特之梦 (NeoPasterDream) +neopasterdream@|新帕斯特之梦 (NeoPasterDream) voidcraft-phase-technology|虚空工艺 (VoidCraft: Phase Technology) powah-cable-orb-extension malisis-doors-reforged rack-it-up@ lychee-oritech@ - +spellweaver2|织法者2* extermination@wotw-extermination @trims-expanded auroral@ @@ -27320,7 +27320,7 @@ the-summoner-revived-raid @moon-spire|月尖塔 (Moon Spire) @enchanted-display|附魔词条折叠 (Enchanted Display) corpse-x-dragon-survival|遗体 x 龙之生存 (Corpse x Dragon Survival) -dragon-barrel-roll|龙之生存:滚筒飞行* +dragon-barrel-roll@|龙之生存:滚筒飞行* citadel-legacy schematicians-goggles@ create-aeronautics-x-curios-api-compat @@ -27359,8 +27359,8 @@ iskanderts-library@iska-lib vintage-resourcify@ hp-hariplayer@ harimt@hmt-harimultithread - -nearly-enough-items-nei +neofontrender|焕新字体渲染器 (Neo Fonts Render) +just-enough-items-legacy nvlic2machines @sableedit the-last-day@the-last-day-reset|最后一日* @@ -27415,7 +27415,7 @@ create-balanced-flight-fork-neoforge simplecables76@ jei-gateways pintonium -@kaleidoscope_sculk|森罗物语:幽匿 (Kaleidoscope Sculk) +kaleidoscope-sculk@kaleidoscope_sculk|森罗物语:幽匿* lotr-modifier-highlights|魔戒装备修饰高亮显示 (LOTR Modifier Highlights) vista-aeronautics-fix@ let-fish-love-reborn|鱼各有所爱* @@ -27426,7 +27426,7 @@ lotr-anvil-reforged|魔戒铁砧重铸高亮显示 (LOTR Anvil Reforged) lotr-journeymap-bridge|魔戒旅行地图联动 (LOTR-JourneyMap Bridge) ae2-meteorite-addon|AE2陨石附加 (AE2 Meteorite Addon) async-locator-refined@ -just-dire-things-forge +just-dire-things-forge@ fluid-pipes@fluidpipes nekoration-remastered|猫咪装饰:重置* @chislate|锲石 (Chislate) @@ -27434,7 +27434,7 @@ nekoration-remastered|猫咪装饰:重置* more-zombies-legacy|更多僵尸 (Mo' Zombies) lotr-horse-heal-alert|魔戒马匹喂食提醒 (LOTR Horse Heal Alert) the-moder -reborn-core-unoffical-1-21-11 +reborn-core-unoffical-1-21-11|重生核心 [非官方版] (Reborn Core [Unoffical]) @twsmp-discs @economicmarket|经济市场 (EconomicMarket) @@ -27469,7 +27469,7 @@ colonylink|殖民地链接 (ColonyLink) hopper-upper @noxlights -@catframe +catframe@ yawningnekoapi savemyrecipebook|救救我的配方书 (SaveMyRecipeBook) @@ -27544,7 +27544,7 @@ emi-recipe-pin@ @dimension-tickrate|维度Tickrate (Dimension Tickrate) - +pulsar-lighting-engine immersive-mechanic neostructuremapper@|结构注解 (NeoStructureMapper) pretension @@ -27569,7 +27569,7 @@ viscriptrecipe@ @deathrollback|死亡回滚 (DeathRollback) ic2radbauble -@step-crafter +step-crafter@ gazetteer@ eclipseui@ @@ -27603,7 +27603,7 @@ oraculum mek-energistics|通用数据* oraculumworktables|神谕工作台 (OraculumWorktables) just-enough-tooltips@|提示酱* -twist-mod +twist-mod|扭曲术士 (Twisted Warlock) kaf-hud@ perdimkeepinvfix botaniawootaddon@ @@ -27611,7 +27611,7 @@ flower-picking@ datatip the-curse-of-ramses-ii@|拉美西斯二世的诅咒 (The Curse of Ramses II) minecraft-mob-armor-mod -mobspawnswitch@|生物生成开关 (Mob Spawn Switch) +mobspawncontroller@|生物生成控制器 (MobSpawnController) @farmers-sellingbin immersive-storms@ jeirecipemanager|JEI配方管理器 (JEIRecipeManager) @@ -27624,7 +27624,7 @@ bloodmagicwootaddon@ deployer@ repackaged@ -scp-001-when-day-breaks@scp-001,-when-day-breaks +scp-001-when-day-breaks@scp-001,-when-day-breaks|SCP-001 - 破晓之时 (SCP-001, When Day Breaks) eat-with-ease@ happy-ghast-improvements@happyghastimprovements agritech@ @@ -27653,12 +27653,12 @@ ifwootaddon@ aeronautics-propeller-blur@ advanced-ore-deposits@ create-compact-transmission@create-compact_transmission|机械动力:紧凑型传动 (Create: Compact Transmission) -@retromod +retromod@ advanced-turret|高级炮塔* -@capitals-a-monarchy-mod-for-mca-reborn -@better-recipe-book-(extended)|更好的配方书(续作) (Better Recipe Book (Extended)) +mca-capitals@capitals-a-monarchy-mod-for-mca-reborn +better-recipe-book-extended@better-recipe-book-(extended)|更好的配方书(续作) (Better Recipe Book (Adorable♡Girl aVa Seriously Extended)) auto-log-remover@ bettertabinfo@ adkm @@ -27730,7 +27730,7 @@ sayuki|咲雪随想 (Sayuki Mod) forget-me-crops@forgetmecrops vanilla-golden-head cc-sable@ -@cnpc-sophisticated-backpacks|CNPC商户扣款精妙背包 (CNPC Sophisticated Backpacks) +cnpc-sophisticated-backpacks|CNPC商户扣款精妙背包 (CNPC Sophisticated Backpacks) potato-power-craft honeyexpansion-reborn|蜂蜜拓展重置 (Honey Expansion Reborn) save-my-ankh-shield @@ -27767,7 +27767,7 @@ bluemap-ftbchunks foxdarkmagic@ fox-power-converter@ capture-nets@ -dynamic-waters@dynamic-waters-realistic-flowing-rivers +dynamic-waters|Dynamic Waters: Realistic Flowing Rivers - 暂时无法访问¨@dynamic-waters-realistic-flowing-rivers better-than-boats@better_than_boats realcraft-mcexpand|真实工艺 (Realcraft: Minecraft Expanded!) @@ -27814,7 +27814,7 @@ stairway-to-heaven@|天堂阶梯 (Stairway to Heaven) dungeons-dimensions-nether@ flight-kick-bypass@|飞行踢出优化* funky-effect-lib|奇趣效果Lib* -@cnpccurios|CNPC可穿戴饰品 (CNPC Curios API) +cnpc-curios-api|CNPC可穿戴饰品 (CNPC Curios API) jei-structures @extended-storage|拓展存储 (Extended Storage) remote-access@ @@ -27831,9 +27831,9 @@ vintage-delight-kots|腌渍乐事 - 耕云钓月版 (Vintage Delight KOTS) together-forever-unofficial cloud-glass@ shield-hider@shieldhider -golden-dandelion-mod +golden-dandelion-mod@golden-dandelion-port pakko@ -inventory-profiles-next-backport +inventory-profiles-next-backport@ structure-tools@ ae2lt-packaged-pattern-provider@|闪电科技:封包 (AE2LT Packaged Pattern Provider) low-latency@|低延迟* @@ -27842,7 +27842,7 @@ tonten@ tpaauto@ healthbar warborn-renewed -redemptioncodefabric|兑换码Fabric版 (RedemptionCodeFabric) +redemptioncodefabric@|兑换码Fabric版 (RedemptionCodeFabric) @eftlm_weaponofmiracles|史诗战斗车万女仆:奇迹武器扩展 (EFTLM_WeaponOfMiracl) createatomic@ cobblemon-battle-extras@ @@ -27870,7 +27870,7 @@ malisisdoorsreborn@ mp-moogs-paths@ pickaxe-armor-trims recipe-crafter -wavify +wavify@ magnet-hoppers@magnet-hopper better-farmlife@ @realplace|真实置物 (RealPlace) @@ -27914,7 +27914,7 @@ dropsweeper@|掉落物扫地姬 substratum|基质矿物* @borderless-fullscreen mortis-fix|毋畏死亡 (MortisFix) - +@prefab-custom-addon|预制建筑附属 (Prefab Custom Addon) utilities-plus@ untagged-untitled did-that-hurt@|疼吗? (Did That Hurt?) @@ -27969,7 +27969,7 @@ luminabox|流光音匣 (LuminaBox) mochas-complicated-dungeon bridge-worm-reborn@ goety-transcended|诡厄巫法:超越* -apotheosis-artifice|神化-巧技* +@apotheosis_artifice|神化-巧技 (Apotheosis Artifice) potion-bar@ soph-upgrade-addons@|精妙背包升级拓展* @@ -28022,7 +28022,7 @@ symbiotic-swiss-knife@ just-what-i-needed-mod|刚好是我所需要的* ranchable-fluid-cows - +lingua-peripherals@|语言外设* hardcore-disabled-fix@hardcore_disabled_fix|极限模式禁用选项修复* electricityinnovation colored_water@coloredwater @@ -28037,10 +28037,10 @@ frame-companion villagertradeedit@villagertradesedit|村民交易编辑 (VillagerTradesEdit) no-more-meshes@|不再有筛网* create-neapolitan@ -create-improve@|机械动力:改进 (Create:Improve) +create-improve@|机械动力:改进 (Create: Improve) rsoncreate|存力合作 (RS on Create) -create-redstone-link-gui@|机械动力:红石信号终端GUI +create-redstone-link-gui@|机械动力:红石信号终端GUI (Create: Redstone Link GUI) create-vvvf-simulator|机械动力:VVVF模拟器 (Create: VVVF-Simulator) create-nowheel@ kroiautilities@kroia_modutilities @@ -28055,10 +28055,10 @@ farmers-cutting-collection visuality-reforged-backport magma-lucky-block creating-tables -betterweather-renewed +betterweather-renewed@ rpg-rpgae-neoforge|RPG天启进化(NeoForge) (RPGApocalypticEvolution) rebind -bosses-rise-intro-skipper|跳过剧情等于跳过人生* +bosses-rise-intro-skipper|首领崛起动画跳过* ancient-tweaks|古代调整* deltaforceskills@delta_force_skills|三角洲技艺 (Delta Force Skills) @@ -28066,13 +28066,13 @@ tacz-40k@tacz40k carpentry-chisels@carpentry-and-chisels sky-islands@skyislands|空岛世界* -more-withers +more-withers@ @itemcommander spooky-foods@ peaks chemlib-neoforged particular-reforged-backport -@mtr-traffic-addon +mtr-traffic-addon@ mundus-magicus chest-tracker-forge-edition @@ -28099,7 +28099,7 @@ ef-cz|史诗战斗:巅峰 (EpicFight: ChampionZone) world-preview-tfc@world-preview-terrafirmacraft|世界预览:群峦 (World Preview TFC) genesis-world-previewer-pregenerator@ goety-dargui@|诡厄巫法:黑暗祭坛GUI (Goety DarGui) -radiation-area|辐射区 +radiation-area|辐射区* galtc chestcavitytweak @@ -28130,28 +28130,28 @@ pvputils-fix@ baking-process@|TW的烘培工艺 (TW's Baking Process) @neko_tech|猫猫科技 (NekoTech) -taczs-bizarre-enchantments|Tacz的奇特附魔 (Tacz's Bizarre Enchantments) +taczs-bizarre-enchantments|TaCZ的奇特附魔 (TaCZ's Bizarre Enchantments) just-enough-freaky-additions-jefa@jefa numen-ai@numen|言出法随 (Numen) irons-simple-blood@ functional-chemical - +@elytra-trails|鞘翅尾迹 (Elytra Trails) metus-oblita twilight-forest-travellers-set-plus|暮色森林:旅行者套装增强 (Twilight Forest: Travellers Set Plus) - +@fabric-bedrock-miner @packwiz-modpack-loader|Packwiz 整合包加载器 (Packwiz Modpack Loader) wavey-capes-legacy|飘扬披风:传承 (Wavey Capes (Legacy)) - +xxsx-smart-core|Xxsx的智能核心* exposure-space@ @weathereffect|天气效果 (Weather Effect) -@swapkeyboards|交换键盘生存!-幽幽的奇思妙想 +@swapkeyboards|交换键盘生存!-幽幽的奇思妙想 (Uwu Swap Keyboards) -@avaritia-nirvana|无尽贪婪:涅槃 (Avaritia:Nirvana) +@avaritia-nirvana|无尽贪婪:涅槃 (Avaritia: Nirvana) bird-watching|观鸟* @infinity-item-editor-re|无尽物品编辑器:重生 (Infinity Item Editor Re) plumes-more-arrows@more_arrows|更多弓箭/更多箭矢 (More Arrows) @@ -28183,7 +28183,7 @@ death-muffler-fix|死亡音效消除器修复* simplifygamemodecommands op-commands@opcommands new-better-combat@|新更好的战斗* -the-db@ +the-db|The DB (Horror) - 仅旧¨@the-db|The DB (Horror) - 最新 emi-unofficial-port-unstable luminanceo ghostpig-jar@ghostpig.jar @@ -28195,11 +28195,11 @@ quick-stack decorative-blocks-reborn@ -@create-villager-labor|机械动力 :村民劳工 (Create Villager Labor) +@create-villager-labor|机械动力:村民劳工 (Create Villager Labor) mca-romantic-expansion@|凡家物语:浪漫扩展 (MCA: Romantic Expansion) @mononym server-i18n-api-reborn@|Server I18n API 重生版 (Server I18n API (Reborn)) -potion-api|药水 API (Potion API) +potion-api@|药水 API (Potion API) @safeparrots|安全的鹦鹉 (Safe Parrots) simple-world@simple_world|简单世界* strut-your-stuff@ @@ -28207,7 +28207,7 @@ ii-dust-trench|兢II (Dust Trench) holy-forging@|神圣锻造* -twilight-spellbooks-irons-spells-n-spellbooks +twilight-spellbooks-irons-spells-n-spellbooks|暮色魔法书 (Twilight Spellbooks: Iron's Spells 'n Spellbooks Addon) cobblemon-utility-ivs-evs-more-no-recipes @bob-fluid-translator data-recipe-generator@|数据包配方生成器* @@ -28216,16 +28216,16 @@ ooundeadexpansion re-retro-damage-indicators@ forgotten-relics-re|失落遗物学:重现 (Forgotten Relics RE) visible-barriers@ -nomads-delight@ +nomads-delight@|游牧乐事 (Nomad's Delight) caves-delights@ momentum-parkour@|动量 (Momentum) - +@foodcraft_unofficial|食物工艺非官方版 (FoodCraft Unofficial) no-double-eat@ iamzombieq@|我是僵尸? (I Am Zombie?) villager-industries@ - +btpmod|更好的战术据枪 (Better Tactical Presentation) - 已停止维护¨@btpmod|更好的战术据枪 (Better Tactical Presentation) vanillin-fly server-waypoint|服务器路径点* world-name-terrarified@ @@ -28238,10 +28238,10 @@ trimmable-create-armor autoafkfish@|自动钓鱼模式 (AutoAFKFish) dummycritfix@|试验假人暴击修复 (DummyCritFix) @armor-nerf|护甲调整 (Armor Tweaks) - +adventure-power@adventure_power|冒险的力量* bossspawner -crystallineflowersearch@|晶化花搜索 +crystallineflowersearch@|晶化花搜索 (Crystalline Flower Search) shield-banner-fix@ nekomusume|猫娘* gone-fishin @@ -28254,18 +28254,18 @@ thermal|热成像* ae2-favorites@|AE2收藏夹 (AE2 Favorites) irissearch@ neoshot@|高清截图NeoForge* -rival-channel-city|争锋频道:我的世界城 (Rival Channel Minecraft City) +rival-channel-city@rival-channel-minecraft-city|争锋频道:我的世界城 (Rival Channel Minecraft City) @locator-heads @lan-enhancer|更好的局域网列表 (LAN Enhancer) big-charlie@ -@tinkers_organ|匠魂器官 (tinkers_organ) - +@tinkers_organ|匠魂器官 (Tinkers Organ) +@eabs-simple-fast-food|E&B的简单快餐 (E&B's Simple Fast Food) fkgauntlet@|去他的下界铁掌 (FkGauntlet) etbw2-curios|暗影饰品 (EtBW2 Curios) territory-record|领地记录* -kaleidoscope-old-tavern|森罗酒馆:老牌酒馆 +kaleidoscope-old-tavern|森罗酒馆:老牌酒馆* tlm-ender|车万女仆:末影 (TLM Ender) baubley-elytra@ @@ -28275,24 +28275,24 @@ backpackbubbles-cre betterloot-zibura@ chronicle-engine-rpg-framework|编年史引擎:RPG框架 (Chronicle Engine: RPG Framework) sable-x-little-tiles-create-aeronautics -bye-pregen +bye-pregen|再见?预生成! (Bye?Pregen!) tree-hollows-forge|树洞Forge版* yetrealm|未至之域* @autoeat -skilltree|技能树 (Skill) +skilltree@moreskilltree|技能树 (MoreSkillTree) find-me@|找到我 (Find me) darkaltarui|黑暗祭坛UI (DarkAltarUI) @technomod ahznbs-ranking-system-addon -kingdomcome-combat@|天国的战斗 (Kindomcome Combat) +kingdomcome-combat@|天国的战斗* mining-forks dodgebullettime@|闪避子弹时间 (DodgeBulletTime) f1-racing-mod@formula-helmets|方程式头盔 (Formula Helmets) micro-h-i-d -voxlink@ +voxlink@|沃克联机 (VoxLink) create-voidway|机械动力:虚空通道 (Create: Voidway) @cbc-locometal-armor|机械动力火炮:火车装甲 (CBC: Locometal Armor) @cbc-terminal-ballistics|机械动力火炮:终端弹道学 (CBC: Terminal Ballistics) @@ -28354,14 +28354,14 @@ sorcerer-of-madness-goety-addon @magic-mtr universal-shops@ window-fix@ - +chiseled-enchanting-table-unofficial-port|纂刻附魔台—社区版 (Chiseled Enchanting Table—Unofficial Port) rotten-recycler@ handful@ sky-logistics|天穹物流* simplest-paxels@ +liu-new-recipes|刘某人的高版本配方 (Liu's New Recipes) - -@gtp +grand-teleport-gtp@gtp utility-drawers@ mod-toggler wearable-backpacks-newlcraft-edition @@ -28383,7 +28383,7 @@ gpushift@ three-dimensional-experience endcrown|终焉冠冕 (EndCrown) @csgo-box|CS2 开箱 (CS2 Box) - +@sakura-journey|樱花旅途 (Sakura Journey) ethiums-delight@ create-aeronautics-harness azimuth-api@ @@ -28411,7 +28411,7 @@ bbs-cml-edition@ jei-biome|JEI群系信息查询 (JEI Biome) jujutsu-kaisen-jujutsu-verse - +@litematica-extra ender-io-pressure-conduits@|末影接口:气压导管 (Ender IO: Pressure Conduits) dashpanels@ blockprint-link|蓝图连接 (BlockPrint-Link) @@ -28426,9 +28426,9 @@ feathered-friend@ coffee-craft-unofficial-port|咖啡工艺移植版* cinematic-respawn@ - +w-cryptid-cove-mod|W的神秘生物湾 (W-Cryptid Cove Mod) take-a-seat@tas -annihilationbladeex|湮灭之刃-非官方重制版 +slashblade-finale-blades-anctum@annihilationbladeex|拔刀剑:终焉藏剑阁 (SlashBlade: Finale Blade Sanctum) @uclearwastedisposal|核废料处理 (Nuclear Waste Disposal) scorched-guns-maid-compat|赤焦枪械:女仆兼容 (Scorched Guns: Maid Compat) @@ -28437,12 +28437,12 @@ pvputils@pvp_utils glowing-emissive-ores-definitive-edition@ corail-pillar mosaic-carpentry@ -antarchy@ +antarchy@|安塔奇* @simple-durability-tooltip dream-sakura-blue-archive|碧蓝档案光环(梦樱版)* - +exbingo @impsmtc|Iam 音乐播放器:系统媒体传输控件 (Iam Music Player: System Media Transport Controls) attribute-adventure@ fast-items @@ -28457,20 +28457,20 @@ hunters-equipment advanced-botany-rebloom|高级植物学:重绽 (Advanced Botany: Rebloom) sway@ isd-irons-spells-delight|奥法乐事 (Iron's Spell's Delight - Iron's Spells Addon) -bielggs-spells-addon +bielggs-spells-addon@ armors-api|护甲组合API (Armors API) apothic-infinite pokemontcg@pokmontcg -parasites-delight|寄体乐事 (Parasites' Delight) +parasites-delight|寄体农家乐 (Parasites' Delight) murmora -projectee +equivox modern-cave-terrain|现代洞穴地形* create-randomize-filters@ create-phantom@createphantom|机械动力:幻翼物流* -create-cosmonautics -create-advanced-optimization -cbcmw-recipe-fix|机械动力火炮现代战争:配方修复 (CBCMW recipe fix) +create-cosmonautics@create-cosmonatics +create-advanced-optimization@ +cbcmw-recipe-fix@|机械动力火炮现代战争:配方修复 (CBCMW recipe fix) @beggars|乞讨者 (Beggars) norecipebook@norecipebookmod visual-item-edit@|可视化物品编辑器* @@ -28483,7 +28483,7 @@ mod-migrator|Mod迁移* shine-vfx@shine more-animations -verity-je +verity-je@verity-je-official directauth one-enough-damage@ advanced-tfr-rocketry @@ -28494,7 +28494,7 @@ always-hostile-animals@ verdant-harvest@|丰年* liteblur -e33epus-s-chatscreen@|E33EPUS的聊天界面 (E33EPUS's ChatScreen) +e33chat@ sound-culling safe-spawn@safespawn timber-frames@ @@ -28504,28 +28504,28 @@ modern-apple-cows@ anvilcraft-dingdongji@anvilcraftdingdongji|铁砧工艺:叮咚叽 (AnvilCraft: DingDongJi) @math-function|数学函数 (Math Function) -inner-voice|心声 (Inner voice) +inner-voice@|心声 (Inner voice) exlines-awnings@ nuclear-bombs-mod packdev-toolkit@ inspect-animations@ red-bits@ indestructible-fix-for-modular-bees - +@universal-survival-kit|通用生存套件 (Universal Survival Kit) enchanter-tools mut-create-power@|更多锻造模板:齿轮力量 (MUT Create Power) @create-schematic-preview aeroengine@|机械动力:航空发动机 (AeroEngine) -too-many-tag-viewers +too-many-tag-viewers@ agony-forge - +casual-copy|随心拓印 (CasualCopy) deep-below@ -chessboard|棋盘 (Chess Board) +chessboard|棋盘 (ChessBoard) abundant-vegetation|枝繁叶茂* educate-your-parrots@ kaleidoscope-doll-plus|森罗物语:玩偶+ (Kaleidoscope Doll +) -seven-stars-api|七星 API (Seven Stars API) -of-beasts-and-wild-things|荒灵野物 (Of Beasts and Wild Things) +seven-stars-api@|七星 API (Seven Stars API) +of-beasts-and-wild-things@|荒灵野物 (Of Beasts and Wild Things) horologium-positioning|时钟定位* digimod-cobblemon@ @taczlevel|TaCZ 等级 (Tacz Level) @@ -28537,34 +28537,34 @@ hear-the-wind@ fov-effects-backport future-shops @sky-whales - +musicbridge|音乐桥 (MusicBridge) additional-planets-a-galacticraft-addon galacticraft-boobcat-edition|星系Boobcat版* -transparent-window@ +transparent-window@|透明窗口* twilight-forest-finale|暮色森林:终之乐章* -building-saver@|初始小屋* +starter-cabin@|初始小屋* efab-unofficial|EFab 非官方版 (EFab - Unofficial) true-power-optimization@ - - - +@handycommands_lff|奇妙命令 (HandyCommands) +@anyslottotem|图腾随行 (AnySlotTotem) +@ffcraft daily-bounty|每日悬赏* -ic2-reborn + xiaonuoclean|小诺清理 (XiaoNuoClean) mystical-wildlife@|神秘的野生动物* - -sign-preview@ +@-doublejump-|二段跳 (DoubleJump) +sign-preview@|告示牌和地图预览* immersive-bonsai@|沉浸盆栽* contents-director -miaomodifiesironspellsartifacts|铁魔法修改器 (miaoModifiesIronspellsArtifacts) +miaomodifiesironspellsartifacts@debugging-iron-spells|铁魔法修改器 (Miao Modifies Iron Spells Artifacts) @autopack-director create-wheels-upon-chairs|机械动力:轮椅轮椅 (Create: Wheels Upon Chairs) @@ -28579,8 +28579,8 @@ applied-dyenamics colorcush|千坐爱印* optimized-cushions@|坐垫优化* skillcore|技能核心 (SkillCore) - - +teamytzs-viewpoint|异天庄的鸟瞰点 (TeamYTZ's Viewpoint) +fatepath@|命途* better-jujutsu-craft@ creeper-bats @@ -28600,37 +28600,37 @@ bmp-background-music-player|背景音乐播放器 (Background Music Player) anime-characters wink|眨眼 (Blink) @nitroito-tooltips -champions-optimization|冠军 优化* +champions-optimization|冠军优化* sakura-library @modernmite|现代 MITE (Modern MITE) lc2h@|失落的城市:多线程 (LC²H / Lost Cities: Multithreaded) - +maid-tacz-sbw-damage-fix|车万女仆TaCZ/SBW伤害修复补丁 (Maid-TACZ-SBW-Damage-Fix) @miningfatiguereproductio|挖掘疲劳重置 (MiningFatigueReproduction) lootrun@|搜完就跑 (LootRun) reglass@ - -ecstatic +@rationalcraft|合理配方 (RationalCraft) +ecstatic@ poison-dragons-ice-fire-addon black-gold-alliance@|黑金同盟* watervision@ mrcrayfish-device-mod-continued -twilightforest-re26@|暮色森林: Re26 (The Twilight Forest: Re26) +@twilightforest-re26|暮色森林: Re26 (The Twilight Forest: Re26) groxs-villagers@ nimbus-scabbards reload-only-data @create-vertical-chain-conveyors -god-damn-repackager@|并行理包* +god-damn-repackager@|机械动力:并行理包 (Create: God Damn Repackager) @create-blueprinted create-deep-seas@ data-energistics|数据能源学* -@cnpc-gecko-addon-ce +cnpc-gecko-addon-ce worldnamepacket melon-melon|种瓜得瓜* @@ -28650,8 +28650,8 @@ nuclear-tech-mod-waldemar-edition beyondenchant dimension-corrosion-crystal@dimension_corrosion_crystal|次元:侵蚀结晶* -@wegui|创世神GUI (WorldEdit GUI) - +wegui|创世神 GUI (WorldEdit GUI) - 不推荐,滞后¨@wegui|创世神 GUI (WorldEdit GUI) - 推荐,已部署自动,第一时间同步新 +@copy-item blabby-world@|话痨的世界* mob-battle-plus @@ -28660,9 +28660,9 @@ yrka|伊尔卡* full-metal-combat alarmist|危言耸听* cosmic-will|宇宙意志* -@shades +shades-hollow-knight@shades -oh-my-world +oh-my-world|公式化世界生成器* inventory-interactions@|物品栏互动* item-shadows@ @@ -28679,7 +28679,7 @@ oathbound-relics@ @geo-expansion epiphany-mod@|顿悟 (Epiphany) phantomrains-worldgen-lib|筑界基石 (PhantomRain's Worldgen Lib) - +options-rewriter@|Options重覆写* zombiegamereborn@|僵尸游戏:重生 (Zombie Game : Reborn) paranoid|偏执狂* @@ -28700,10 +28700,10 @@ weapons-of-legendary-monsters-epicfight packs-editor@ - +@-empty-hand-|空手 (Empty Hand) level-difficulty|难度存档化* - +@vianeoforgeplus better-combat-toggler @sbl @@ -28729,7 +28729,7 @@ alexs-mobs-re-legacy woc-render-arts-extra-for-weapons-of-cataclysm fpvdrones @lzxnoneterraria -chainmining|链式挖掘 (ChainMining) +chainmining@chainminingg|链式挖掘 (ChainMining) creepyhorse-jar brokenbots-lost-medias-pentaros visual-creative-tab-editor@|可视化创造标签页编辑* @@ -28751,17 +28751,17 @@ aromara-thaumic-perfumery mace-enchantment2-electric-boogaloo|超级重锤大坐第二季:谁问你了 (Super Mace Slam Enchantment 2: Electric Boogaloo) - -pattern-encoding-terminal-quick-math|AE2:畅起速式* - -jfmuy +@perfectmending|完美经验修补 (PerfectMending) +pattern-encoding-terminal-quick-math@|AE2:畅起速式* +@ancient-city-underground +jfmuy@ @anvilcraft-simpleprocessing|铁砧工艺:简易加工 ender-io-source-conduits|末影接口:魔源导管 (Ender IO: Source Conduits) -loot-all +loot-all@ fakeneko-config outbreak-epca-performance-compatibility mca-descendants@ - +pangu-spacing@|盘古之白* ender-contract|终末合约* secret-rooms-unofficial@|密室-非官方版 (SecretRooms-Unofficial) @chamber-clarity-reloaded @@ -28772,13 +28772,13 @@ milkshade-dynamics@ - +four-seasons-chronicle-of-huayue|四季花月录 (Four Seasons: Chronicle of Huayue) @eeverything_hurts|万物坠落 (All Falling Damage) - +acceleratedrendering-ported@accelerated-rendering-ported|加速渲染:移植 (AcceleratedRendering-Ported) more-cloud-layers@|更多云层* sizeable-foliage@ - +@better-electroblobs-wizardry|更好的巫术学 (Better Electroblob's Wizardry) @@ -28791,7 +28791,7 @@ med-system@ @highlightitem -falsity-mod +falsity-mod@falsity @playerbatch mca-r-polygamy @@ -28802,12 +28802,12 @@ apokinetics@ scs-spell-combo-system-irons-spells-addon|法术连击系统 (Spell Combo System - Iron's Spells Addon) dream-champions|旧梦词条* - +@item-master|物品管理 (Item Master) @disc-jockey-plus packagedauto-wireless|ME无线封包供应器 (PackagedAuto Wireless) @autowork create-cooling-fan|机械动力:冷却鼓风机* -@create-avionics +create-avionics@ create-lights-controls@createlights-controls emf-compat-core@|实体模型特性兼容:核心 (EMF Compat: Core) cynosure@ @@ -28827,14 +28827,14 @@ cauldron-cubes@ @create-empty-packages-fly|机械动力:空包裹飞越 (Create: Empty Packages Fly) mna-integrated-adventures|冒险者魔艺 (MnA Integrated Adventures) @elytra-slot! +colorfulblocks@colorful-blocks|给你点颜色瞧瞧! (Colorful Blocks) +extended-oritech@|Oritech 扩展* -extended-oritech@|Oritech扩展* - - +biffrunrunrun|Buff,你跑不过我信不信 (buffrunrunrun) create-crafts-additions-plus spice-of-life-delicacy-journey|生活调味料:美食征程 (Spice of Life: Delicacy Journey) uwulan@|幽幽局域网联机 (UWULAN) - +@tacz-fixes ice-and-fire-memory-fix|冰火传说内存修复 (Ice and Fire Memory Fix) automuteonafk@ builders-shulkers@ @@ -28849,9 +28849,9 @@ moai-statues intense-horror@intensehorror dungeons-additions - -gt3-racing-pack|GT3赛车包 -f3-f4-gamemode-switcher-for-older-versions +ritual-crafting-table@ritual_crafting_table|仪式合成台* +gt3-racing-pack|GT3 赛车包 (GT3 Racing Pack) +f3-f4-gamemode-switcher-for-older-versions@ @fast-masa-configui @rankboard|排行榜计分板 (RankBoard) @@ -28862,20 +28862,2650 @@ lightload@ tfc-music-maker jade-sable-compat@ -@tierify +tierify@ visual-mod-edit-vndialog@|可视化VND编辑器 (Visual VND Edit) stupid-block-animations@ legend-of-the-lucky-blocks@ chronos-backups@ -spacesuit-required-addon-for-galaxy-space +spacesuit-required-addon-for-galaxy-space@spacesuit-required gcs-gchouston-command-remover galacticraft-respawn-fix -better-paint|更好的画 +better-paint@better-paint-mod|更好的画* tinkers-golem@|工匠傀儡 (Tinkers' Golem) -@refined-storage-cross-latitude|精致储存跨维度 (Refined Storage Cross Latitude) +refined-storage-cross-dimensional@|精致存储跨维度* lush-grass|茂盛草地* @(ssrdfv)-separate-sable-render-distance-for-voxy -sable-assembly-fix|Sable:装配修复* +sable-assembly-fix@|Sable:装配修复* @create-droneworks-simulated|机械动力:无人机 (Create: Droneworks Simulated) -3YV3R=3Xe3X;3X43X/3S]3B'3FY3Ab39i2,[3V^3WE3SG3F43V:3Hp3VH3W}3Tb0012ze35e3V)3Wl3W=3EF3TF3K;31T3YS3KA3O'3Np2!@3Nh3W32z$3MD3G@3Ve3T637{3Qg3Xo2dM3GT2:z3V?2y434(3Od3X]36w38g3Tw3TA39}3XH2o]2uD2rl3Fc2w$3Lf2Nx2Kd2zk26?2M+3G{3F*2S)2Ep3QD3Q[3S53E^3F/2?,2ge3Jt3OA3OU3O:2cU3RL3ON3S?2nC2Kw2k<2su3YA1b*3O$2iz3YR37839e3J=0043XU1%+3OK3LJ3U$2+Y3P*2&+3XW36{3Xy3W;2=;35)3X:2J{3Wa2z3YK2]f3P]3XY3523J'2dI3WJ3BH2]K2zS2Ya3NX3W^2=i3N^2)D2SW3I[3Wz3W@3U03Qj3Ix3T(2]P3N42EP2;33FD2H*2w23Kr2]02m{0063CU3WZ3R:3YG3Uk2}:3Q}3YU2,03Ok32O3Nt35t2OE3Fd3U[33b3QN3UT3Vv3YO3Fp30L3U@3O93T@3Jf3N+37%2+W2%s2Xn2^x2w]2&&3G)3183Tu3K+3R>3X73Xj3T<2Yc3H)2tZ2&^2vr2uy3Vt26%2xR3V#3Kq3V'3IP3Uz2'F3EI3Uv3S(3G+3C{38*35I3YB3UM3XR31u3O#3Vx2)&3P)2,b3RU3MT3J62Jf38u3QG2>P3FO3VE2]&3I836o3S23K,3Sf2:b32k3Sx3EE2&Z3G$3Y034m3T[3W63YD3T/3Ht2WB3L;3V93Ub3Rh0073VY3Eq3U*3Qd3Kj3D70082!n3Ud2;R3F{3Uq32z3JK3Nc3Eb35w3MA34t3Y82n53X}3HP2{z2,93Y63953X)3T+3LT3Tc3Vj3YL3U#2'f34?3Q?3Hh30*3Hv3T,3I53G33XP3VU3SA3X,3Xs3UL3Vo38c3Tg3E23Lp3103XF3P?3QF3J$38^2:%2?:3Aq3UF3W{3K}3XS3Wg3N;2tR00A2hf3TJ3Xp37z3V>3EQ3Uc3Ln00B2(53Ga3BM3003JL2,=3Ot3WG3Dn2F{3W*2/P3Xb2*K3XL3X83X*3J331q3I+3Dp3S+3642*G3Ax3P}3H/2}q32U37>3Vs3D/3Lt2??00C31]3U,3VX2&I3MB2,53Ja30k3Fy3X>2ug3GW3K53MM00D3Ow31O2?L39G3Y72]/00E2Vc2#d3KR2Q83T33892#R33,1=k3Nj3C]3B&2)72p!3P@3I=3Do2[%2?$2>=3P12^l3VM3VN2&?31F2'k3M<2:W38r34632i3Ks3Xm31}3A63F@2Sb3JS3BQ3W<3XT37T3LU3OM3Tz3E;2nv3Db2Rz3M(2jT3G43NT30c3933Pn3OB3N*3OS3KI2y%3W&3HE2$j3AB2X2t{2f!3BK3AX3EZ2}E2=&2?R2I83OP2e+2((3SZ3Sb2kA3CA3SO2:X37(2dS3GQ3Ge3Ce2gO3T;2&>23E)3SV2yu3WM1[Z2?x2^R36d30g3X^2jr3RM2wX3M$3Vu3Mo39]3VV3Ux2$]3Su3013Oj2uw3Gm2*,3H!3T$2(Z3FB3C<2;e3N%2<=3Jr1&s2?31@$2UZ3Au22y1,@2Nl1[H2o53F93B)3PN3C?3Q$3Rb3M}3S!1+a3V<3NF3QT2)q35(2hM22r3MK3Pm2qx3G;2cX3R+3Te2w'2Ye2>,35E2&s3Xn2hL3N32zx2=J33[3Pb3NM3DC3C:2}K2)d3T^3J@2!43A026D2Lc2L^3Xw3Xx36y3Sn3EJ37L33{32(2:r3Tk3WL3RD3W:3Ji2IL2NU3K83F)2,S2#J2,42VO3Um00F3Iz2>j3FK3R42{+3H'2(h1yh1mU3Xv2+@2}}2K82il2}D34i2>63E32r$3Ka3Q/3Xu3I;3U93W%2^e2lj35U3A33I)2CA3Jo2h!25X3L$3DX293V&2uo3DP3C%2%E3GK3W$2!s37w2]b2&732R3L[38J3MZ3TE35{2n%2@<3G?3Wb2(?2qj3P!3O43XZ2#73Ug3Nq3C12Mr2[o3BE2F<2i<2k@2Pw30d2Cw2*h3632v>2PS2kv2vk30D3Hd3R13J>3G/2;t2yz2}72jH2M<2FU2+$3CL2r;2{83RW2RQ27>2t&34T2pD3Dc3N=3Ul2rf3S'3Q^3SM3K[32a2t=2Sn2xa39M2,!1*V2Q:2Y<2nj2U/3993M=3VR2cE3MI3Tn3JA3YW3K@2]839L2>*37v3Gv34R2l63SY3OI3D#35H3LH3U=3Mp3572;}2p$3U}38x3X%39H3IH3Wi3F:3Rf3EW2OQ2:t2mp3Qy3W]2/M3K?38>38)3Rd3O(2!M3I&32m3E*3Uh2UW2pA3WO32G3VA2:k3Hs2;<2,83YF3AI3Lz2(!2Xv2ak30y3Ha2#V3Jp2m<2%/2Ab3SW3Ej3Xq3IG3YQ3S131k3ED38Q3H934h2nK36S3T12r%3P>2'Q2iE3B939*37N2@F3Tr3483ET39B3WC3Nv3QC2'd3BX2>+2,:3Pr3LM36L34]2=c30v2'?2#k3Es2)k2q*2rG3E@30Q2S63S^3L)2E+3AV1+o1!m3M[3H:2)a2}'2k(3Ta3Q#31)3Iv2y&2Yd2,+33n2je2]s2}C2:*2Gi00H31{1]}2{h3QM28U2,#3J13U]2G[37[3K33Qe2>}2xh2(v3Ls3C43GO3VP2za31L3Ut2yM2&!34c2mM2{C3W03DE3M#3Uj3OO3Id2Qe3Vl2iv33]3IZ3Wc36'2ic3AA3R52ye35C2CX2h/1w?3He2}#2![3KN2Tk3W52?v32n3P(3Oh3RA2@C2:{1%n3Nr2i&2[!3G#35k3PW32X2Y73E[1?:3LP3W'35M2[P2B{2*q2=w3IQ2mu2&Y2mo3HU3W225s39^2^?2#z2:l3U52tp3Wu3LL2;]3SX2>73E(3J}3WQ3Df2*L2?c3Ba3Wr3D>2g?2%>3Ci2*n3RJ39O2]@3A42+t2}P3FE2mF2e03LW2kn3923FT35>33W3Az3UY37q2;%3353IV3U/3F52{u3Vk26R38S3I*3M62?+2rM31(2&T3F,2;*39r33c3HQ2>:2'33LN2$t30P3Hz3XN3Lk2ne2h43PF30[2;n2hb2*U2/k3BF3Oa3R}2zQ2=S2or2Wc36@3E&2)v37i2>{3Af3D;3R03WU3W(3HX3Mz3O12p#2(>1>/3B%3TG2'/2=D2}639n39;2&i3Un3FX3SQ2H]2<+3KJ2xM2fK2pm39!3Op2T2[T3NG2q12*g2gP2GI2AO2;S2:@2%M2p*3U{3Ld3S93HY3KT26@34p3GY2!L3EK3Dj3LG2%J3Qs3EB35,2Li3WK2$52)>33^3VC3LY2j<2(w3Bi3O{3Tf2zz2X228z3XX3S$2Y92(j2&z2o/2:f3PR3C72Fg2j#2sU2C22!Q2nF1eL3U;3AL3Lu2h62>d3Jd2}@2H$2!(3Li2Z52e#3Hb3S&31E3LB3K23S;3Jb3LS2sK1({3PV2[52X;2er3KH2}U37u3Md3QV35O3XJ2[#2H)2,m37m1:Q3882Rq3DO2,23Q+2qo3TT3Vh3X(2;73X'3G533#3Kd3DM2@T2z02az2$(1}%2622u33Fb1ut1yO1;[2OT2=Z2CY2sD3Dm2*{2?W35K3Ou3AR38M2,C32C34e2:732Z3Ui3Uf2yc2$T2}%34X3Tl3Ny3Qq2Sc2w(2Cv2Tu38'2bj2#X2Ku37c2^G2Zt3R^3TS2Z(2)U3Xi33V3O@3AO2:a3A:3X{2%%2:E2=t3Cz3GS3SC3Q<3Rc2h&2eH3713TZ3We31n3M>3323IT32b20H3XB38a2!$3I$29738<2Mc2!93Wv2'<2el2p;2?s2&:3MP3N#2c92lK2VN2>O2Xl3Qc2,/39j36,2:I2]Y2J+3Rs2jF22ds3Q!3D[33!2>#2=^2xW20Y3JH22x83OT37=3Jl3J,2gG2bP3Ng3NC3LD2G23PI2=z2UK3R,2vW3FI2KB2(333u31<2CV2#w1},2Hz3D(2XC2pd212k32{#2iX38U1[n2{x2ck39x3V@2s'2Xi3AP2ZJ2])3Q,2sN2e'3W>2I;1od2s$2n(2Vd2A22ok31@3NS2<13Fo3J82&43CX3Cd3Oe2UA2Xe3Qm1n=3Io3D52}L2:g3U'3B#3H+33a2rw3F$2E,2^Z2*N2pv2D33WV38,2Vv2'R1'62T{2cj2{^3Mj2+f3Cp2gE32t3P22'I35'3XD3Q%3DW3YN3J!239c2nw3Ry3Sy3M930W3IW3MG2dD1aL2H72)13M83O!38V2473M03JP2x#3TP2D02n^2?G36P3Fq34>3Gc3Pv33<2{J2!m3Re2+Q2/w3Tv3SH2!U2)%2,E3E,3C;2v(2[{3J01}z36>37@2&k33+38]2I?2}(35R2#C2yV2i@3I92!{2K>2*41}S00I38+3JY3R{35=2xP1vd3D)3Gr3H%2/]3JF3G>3V03U12>82{=3Mt3Q)3CF2)L3Q02<*1lg3VG3I:3RS2g;2k/3L/3Q&2s+2uj2$n3DT23}31w2bW1yA3P<2?y2oD2^W3SD2XH2{v3963VS3Jj39a3702Su2^'2&e3Dq3J?2e72vt2Ig2XA2=o2$92Ei2AH2rb3A{28l2AE3D]2&F2Q@1:V2pn2yo3Kb1lZ2x^2?;2hR2}Q3Ir2x:2uW2bi3Ip3B839z3Dk3Wt2R@2LJ2b3Ms2([2wc36(2^r3N{37l2/'3CQ31t1:(2,*3Oc3W92%g3Ec3U22ty2Oo3Vb1'(3O62>F2Bo2eI2Z82K;3OW3YJ3Wf2*P3R%2v:30I2Zx3Ua3NQ2oQ23)2+#3Wp3TL26530p2;93Qt2=f3Cs2%#3CZ38Z3Q43T#1yE2Ft1?u38i2OP3QQ2Rn3373I#2qn2S#2'52a12}F3Th2]k2]F3N}2/G2v32o&3R/38E3VO3V{2lR2#O3592ks2zW32d3M:3C,2/Z39>3L]2M*3Vr3Cg3LI3LA3T43On2bS3552rW25)2XE2/v2)*3H[2U{2sy3Nk2^52bn32?3Sl26i3OL2Pd2<43243U:2><3H^2b$35#3F02)E3OF3PX2vq2/S2>^1iz2Dd2wq3B^2r}2}]2W43P72m*3O820e2nr3O33GH3Uw3Bm34l2P{3U72GM2]z2(H2sk2rr3Ct2!B1=&2mx3HW34C2)83Ib2wg3GC2qa3Kx2xk38W2'M2+l2e)2p31p;3SK2BQ1j91gY3TQ2qI2!r3M21Ir1841rm1!&1?E19Z1a61Nw1D#3Q22)+2YB3U62fE2f/2dV2!J1ey3M{2*63VI2gr3IX1@'2}d3Tp2Y^2zq2fx3942$c32^3O?2k%28:3Qp3Vm2Vu3531)D2%f2bp1'K1Vo2s@2MN2Df1{52XU1sz1!y3E633s2L<3Rz3JT2o=3Y43UN26c2/l2W?3RX2^L3Bj1z,2tc3Mu2#K3X530@2,>00J2y,2*R3Ad1@62w73V=00K2}B2>c2;C2^g2oj3JW1&>2%z3T?2G@3PA2E<2=Q34K3BI3861aZ24o2PE2Tr26p34O3RO3F73JN2a82^a2A%2]e1WQ2U82yr2Uz3Lc1]u2+D2i$32L22?'1%12'L3H525!2Zv2)Z2rO2u)2=[2Sf33i3E72AF2zZ2&L3DQ2#N2o)2v52?M3JI2+H37^2zT2KD2=@2{a2I12l$2ZW2J32?%2L#2Bt35@2V12fV2>m00L2d)2GV3Vq2Y,3Cm2L*30=2E32oL30}3Q;2'+2;H2dK3Gp3Q*2b83Xa3PT1tT2V83L333d3Ds3Ek2Uv2wV2vE30'39d2;13BO3Wn2Gj2C{2JL2,H30H3Hy1}m2oc2[D3XK3Ob3O>2@y3NV3T%2rs2>22}v3Tq34s30S2}p2'S3L91}(36[3Hi2${3B=3JJ3I13NE3W#2@m2Z339A1xZ2[B2oU2]2/O2{(1)]28,2FP3Iu2B12'D00M2mb2/W2q43Ch37Y2v'2mI2q3Td2qt2,n2zu2gC2ZY2pj3Mm2&g2P42Zj00V00W2R23S*2Wq3If1)A1/w2aa1U82h[1$a30X34f3N@3R<2#f3E<2%F2b11}G3Q:3Ic2;(2x=2u?3BU1/{2YF2wQ2$k2gD2ZK2hV3Q53Gn32%37x3Mw32:2HI1)K2]W24M2f02mC3AG2(+1[O2W52yU1$?23N1%{2px2^V3Nw3D23763Xl21J2SR3N837H22q2Ra3R!2d:3T*2w!3O72oe39U3Gb3DF2'13Ws2u^2mZ29*2D@2Lx2IX2!72{G31A3F<1UY1{@2?{3Rg2#037]22,3IL2U:3C*2}s2{w3Am2V92>?1'X2(X34b2w{2'w2+T3R233O00X2$L2cD2/u3Vf3Iy35W2fR3GX3JM3G]2Mp263Fa2N:2]U2@62!02H<2mT2,@1o:2&c1W>2uL33A3A22)c2,i3XQ2{K2F52&(1]e2*(2=,1'+2$U1;U3D<2!f2+q2=12Z;36E3PM2PZ2HX2IW2pO3TW3Tj2&V2mK2^22)I2%p2{y2hd2[[3Dd2#52XI2FS33M3QP3Mq2C&3Jw34J2=L2}83Sc2R62#Q3RC2ju2;o2uP2$X2>u2<(2*02=}39[3Cr2?91#'2MG2VM2&D2>C3V}3H<2p'2b,3CT2*y28W2q]2v+2p]3042tP1>c2rS2,d2nI00Z1Mp1ew3272)i1aI2[J2}42Dq3X92@H2^+2sl3Jx2811vj3B@28*2#D2;822}2Z62VD3NU2+N2tr3Lb1:?2<;1'}2ik2pG2T>2Q(1}D2;X2$z2J'2YU2@c1:v3S<2zH3NW2>V3B(2W;2KC2I#2iR1c)2vV2^#1(92eF3YE3E41*k1TI3Bp2tM3CV3472mi2*O2:o2hU2qu21S2:53Kc2zX2*J2V}2J,33D2C=2Lp2en3Mb2rn2#M38y1;#2p+2wx3AQ2:+3QU2Kb2UO3Le22!C2yK1##2(Y3Vy1?&2kW3KG2+i2zt2a3Px3Om38(3CB1[E38B2/b3TU2[j3Js1E723?30t3Nm2yx3X[3S63D!3P,3Ty2h*2O!2cc2<)2@S3FQ2$F3PQ3Wq2&h2&;3Dy3UW2y32:p3Gu3GZ3S/3Sv3Ss2?F2sv3N<33}2{U2^%3Lw3D@2>I35y3SB2=62$O3P426+3AY2z^2zo2k!2X^2@=2T]2i:30G3RB2qX3N$3TH2^32w&3283OY2vZ2yj1u:2>J2e}2au2gh2=N1S&2:?3UI11>x3S)37,36T3Bk1^^3G<2yk33j2,336D2'$30i2L13X238Y2p22$>2<:2lG31B2e$1;s2P)2l:2kL3RI2,M2y/3Xz2&@33v2u]3U<2}[3Jv34^3Sk2ls2+=31*3C83UH3Rw3GI29^2;:2^p2R,3Qx3IU2N$2$?2)g2;E2@N22b2?P2s%1,:2v63A%2x?3ER24R2/r2%33CW31H2{c1WU2:T38C2pJ2Q$1t)1WZ22%2/;1!Z2j)2gp1og36;2h^1]53AT3M+24w3L#3WR2:H2sg3R$3GD2mk2t522i2G^2$l21A3IF2h52/>2VK2'u2TX3Ei2Ua3422&22Ym2GB2iA00b3AD1:42jB2&u2lW2552U;2^F1a*2a33Xr2j[1:i2;x3H41*J2et2Av2$R2ui29,3D^2m13F(2S423U2O22my3221;e2kJ2493C$2n;3DL1n22[u2a]2o12mc3S71'V3UD2/%3B[3GR2B22,F2zB3It3H22X!1{21mr1]02Tq34Y2+35c2n?28{2?b3U81[)2/$1zK32323C3Sz1<11(,1U52b?39y3BG2:900d2eN3Gk2mL3N51&43M'2!'2tS37A3N[39l2^I3US2QR2Kv3C'2yw2:M2^Y2HR3D636n2^^2^S2GX2;d22iY2h;36B2vi2n}36%29I1^M1>[3UO2f;2uH34o21*/1%k2un2+h3B02O639f2;?2O^2@x3K>34W3QH2W32}/2/:2tq1lv1:y3Oz3GB22]1%A16M2c{2@b31X39'1ru2Aq1>@2]{3M)3V42Vx27$3EX2Qr24W10l2=G32*3P:2h}3Kv1;82&d1Yi2?Z3Cj38s32T3I/1Mr1]#3Eu32M1}W3BR1$}1En1oN2zA1,b2YV2ov2=A2aT2L/3Ju1}53Aa2JJ2iJ2Ze1[z2462a&2?[2wt2hI1^T2v*1mx2Y%2Hp1*=3Ma2hY3L'30l28/1{j2$e1;'2Hx2xg3Mh1%=2ho2Eg1&?2vd2Pb2L@2n&3QJ21'1}O1{G2^]2J93ME2qT2^j2De2So2<#00e2*21*F00f2f72!z2)<1$C2N%2:>2(i3XV2Ik3Ap35L22wA3R'28J3AU2;F2m1ug2lO1f526S1w>2Tt1t:2tx12!]2g)1m52wz2X{36?2nD3B}36A1:%1${2w13N92l>2Te3HC3C[2Ed2,13BT2uI32g2dn2+p2vM2*D3Ll2Dl2^D2{N3B?2Z&2C92wB36U2nH3692D524u2)z2C/2$K2iL2iC2ut2:d2fi2R82bZ00g3DK2jM2*B3DB2hD3Ep1+H36O2?f3DY2%Q2e<2?82Z02>%1wC2$N3Fw30C33?34#3II2qQ2on2B92xF2)W1?y3Oi2vG2fv1N&2Xy2;{2hX39<33%27R2E&1&(2E53Er39F2/71%T2a,3R(31b3F&1,J3Ti2Rt3Kn34M3O52[;2'63Pd31y31p2kM3Sr2iI2!:2+}3S@2=>3D&2y!2ZE2bX33f3C+3FW34$1,E2eQ2$[2jG3FP2a(2=)2[I38X2>A3061QP1&%2(@2C>3K%1rc2;m2ki2(d3PP3L12tE2dc2K{1h:3DV2L3PJ3Kg2v[2v73Pp1l^3C/1m>2p63E]2r'3Qz34=2f{37R2SX2$y3BN2Fk12*Y3I^30M2gJ33U35o1f$3Al2gw3T)2EY2[g2?J3BB3SI31h34P1zZ3F?2HA3F+2*33Ns2:q2#g23G37J2:&3P%2;v2ZV2/,2I@3E?2KT2t;39k1O&3L!2=%3Vz2X=2]S3Fi2z82An1AZ3GJ2}320W2;B3V12d]2C12^72K'2Ky1),3493I@2:[3U^3913SS20b1E2w,2{22TH2tX2012Q'2b<1!U3UA3J%3Bb2SH3FS3BV2*@2+L38f3Ue3Bw3983Q'3Wo3I32^=26P2[K2HL2')2at2}=2ol2SP3P{36I2[&3Mr32q2k?2jX1vM1ZL1'y34Z3M]1&q1;A1[62(]3Si3E82'E2t/2/s3Eh2n!3B;2C32$02Vz2ED36V1%y2;[3Gl1}82pB2@:3UV2,&38j0(t2e{1!$2Ib1/u2Q72(B2!R2}&2qW18A3Us2FN32c2*z2V#3G%2zn32;2y#2+X18$1{E2jO32)1&k2%02'n2c<2cI3M,2Iq3CJ2%^2'@2b{32J3IO1x52)J2(,1^R3Fg2RY1Be1@%1J%1}}1q:1^>1{62lX3A(1f91%42P/2'l24%2jw1}e2{T1zR2n:2%Y2sT1zz37!2z42[}2NP1hF3Rp1o)2VJ2vU3EH2j&39Z2w82p=2O*38l3Dw33X1KJ1u51S71w=2(a25Y27O2kU2F037g2]*3WF2}a2ZF3A=3Bh2C41MX2p52X02qM2Wg2S31Ch1]n39@3QK3EA2m(22J2L[3ES2Qg2W82L93Fj2Si2^n2'a1([2Pr2'e37Z2wr2fd3Ck3HM2eG34!3Ie2:m2BP30#3XE2*$3J)2RP2x43112tz2qg2}x2tN2zf2Ty3682E82?z2#F2$v2:824f2*)3Je2]V2]H2yZ3833Gj3K93R&2s02I$2j]2VP1*p1}u2rZ2zO2c#2,P2r#1&E3Lo2+F3G22Cn2z+3J+2Q{2ST1G>3L&2:$37W2dC35J1p62c}2u@2dY3Mi1Hv1!:1I$1q[3E%37*2l!2zV2!;1'A2(E3EP2cZ2I:2xT2NF3I<2Z<3DJ1Nk2J@2pX16'38P1y426/32&3873KC2k*2z*2j$2dk2e[1Ok2(*1lJ2vo3Vd32Q2,{2:G2=m2QS2hy1Ls2^b26A2%+2&K1<>2#Y3N&2wi3Bg2u41Jo2QX3V32*I3WY1xW2[+2Yf2!y2FO2fs1}y2i>1um2H}2qs1=z1vX3PZ2/=1p]2@{3CO00l22@38w3N!2D?2]j2r332!37I2HM2r&27,3C&2Rf2yv2qB2Oq3MO36N2ma2nB2#)2wo2#e38d2;L3FG2%B2/x2;#2%<1,736g2Nm00m3BZ3IK3BC2^02r25j35+2,<2sG39q1O*1W33D42,Y2qk2ZI23x2eg2r@3G&2tl2jR2%739023I0]S1yX2B)1,A2R026t35:2Qi2X(2tw2u:3E03811vY27S2zP1sL3771nF2t}2GG2No2Yb1)i2$<1kB1oW2/82d92?a2UV2tH2e93LC2F>2*V3R33My1Zl1@:3B:2iW3I%2Ox3By34w2uR2%t1b&3U42xO35P2[U2052Qp2Ob2w52:Z3YH2zF3H;2MF31g39{2zs2Jt2a<3F]35S3BS34/2@L1du31#3Ij2FH2)F2r>3Tx2#;1nA0*m3I,2;l3653E+2{[2#x1l%37f1f{1,c27c33g1&!2e*2Y>1^k2@z1}@1oO36Q2D^2@71[=3F^1Rb2>i1vI3P&2aY2x*2k21z91[Y3MJ3FV2Vf2;M1'%2ni1]K2[@2eX2B02kh2)n2M!36730/37F22z]2f62)V2?C1@42[>39C2Cp3OH2Dv2v?3B$2?22aG2H!1>72F+2n@1)01ha2E@1;938v2)p1:R1,41;,34G31d2T71VZ2pQ3QW1;*1):1b62V=21W2,j3Mf2G52f12+x2t:2'*2Xg2+r2Qa2O72AN13q2OC30(22+37}28G1qK2(k1=s1ul3VF3HG3C33Cv1:Y37U3A@2RZ12?I3Sj1W{2)Q2/Q2[b00n2]E2!232y0]o2JT3Q11(F1e<0>'1[?28%1M&2m91^h3Fl3Vi2IC2%b00o2aE2;w1v:3Bo2%(3D'0{L1g02j+2HE2$'1;c2as2:y29W25S2+V1?F2nV3821)#1p(1$t2F%3D%2^P2zM30Z2wN1xg2pP1iU2/D1u/3Jq3D:2[32+B2%22xl1{p2w;1,u35l1$&2+a3HJ2Pz3K72?}25*31R2Zs3Ft2$:2P72m$2!Y2qq2ba1@01x^2I424h2D'3F22wG3P533l3UE3O^3Ao2sZ1]z1e?3X036+1Tr2Jk3LO3H*1{72==1>e1{}2Hd2s#2;)3N?2]52)P3Gi1h#2@g1v<2s]3E93Kz2v^2s42j02>k2/V2&H15W3Ay2'J35!2KA2MP1E11}x2n92+{2Vs2]T2h32BN2EO29p3Iw30E3Q=2U12qd2+n2F[2Gy2gY2Qf2aS1]32}e3Q82iU3HS1dN0;t1)^2iF2(=2Da2w^2PQ2b'1Hn2ku31D2gU34r2+Z2;c2KL1y#2z=1%w2^!2B32;r2:B1Ze3UQ2FA3QE2od2K?1WR2=b2TM1a[33z2tI2,p2=g2&v1aD3UU1$#3Fe1ZM2T^2?(2[y1cP29r1xy2#(2;W2Vk2jg2>v1)S1=H13Q2cg1nG1=*20L3Gf1%f3EC2M}1M62K#35%2mG2Bn1Th3H@2!E23$1wv2Z!1km1l$2aI32+2O82'T1'0,61bJ1rL2Wo2zC2Kf1+B2)s1wF1OS2vn1%z1)f1Ad3M&1j*2Ks1$b17z0;A0}X1AI1mS1j71&F1[o2WA2sL1j)2Rg3Cb3412DJ2G{2AS2xm32o2Ny29e29N2UP2Kh2KW2Gs2T:1z+1R^1K:2bb29x2BO2?&2Pk3OV1!!2VE3Eg2B^2xu2@&2nd2>h24K36K2)23K(0%r2uM2{E2g/2d&1,s2Pl1vk1xN1Pa1Wd1vO1yb2'X1?g2Wn2}l38b1t]2f=2l)1aC2MQ1y&2mJ22M2lH1nB2o{2z12?12Q>1)*2i01dX32e3Cy1/i3Hx2[d1>k2PW27112{2fu2CL2<<1i51l[1xj223O2@n2uT1qu30R2Zd2us2&,3K=1!K2)$2TF1E925$22t1i92[W2&y2BR2dG2B51n'2?/2@E2v12Z>1n[27t1{l1BI2s32@X2Ov2HV2q010i2o(3ND2;&2,t2Es1#B1+42XK2*a1%81KM2Md2fa2IE2@@2gv1[c2VT2,}2%;2$b2P'2PL2)x2?639N2X12:w1We2b&3W738@2:=32u1]Z3Lx2fM1/72s,2&'2802RF2zN1;X2V<2}W2ss28Y2*H2/[2#&38&3O=2M=2Gl1,z2I'2OF2Qy20B2J/2OZ3NZ2(O2Bw2w[2Uo2d;3Pe2rA2ag1My1L)2x93KZ1'i2y52f@1[^26l35q1}r2682hQ2}O3L^21n2:D2P62W!1]y3Oy38:2;>2s72of30O2b[00p3Qn2P&1&n2X$2N11dv2YL2'g2s61n)1=630B2;j2:K2kj1;E2ZN2*A2kX2#q3NB2H{2GL3B32)A2{02x]2NE2fS2sQ2/01+L2!G1%g2TE1W:36Z2Q]2Il1)I1n}31c2)?3Sw2cq1x+29?2E*33(2Ap3790]i1JK2qG35Q2}w28c2pI2OH2583R)2[x1b:2,k3Ol2(;3Mk2ad2@I2{51m$2OJ2ez2dd2}m2Q)2d43AZ2Lw1KZ1%i2SN3KY2H52yY2R+1#Z25K2^K3G72)[2GY2jV3A81yt2M:2wS3742rg1h71'q2pl2F82kq2!o34&2hW1NZ2&M3M^2ld2?o2,^2$42Xp2x%1^H2]10<72}11:J2&S2GP1#=2mh2+d2Yu23q1wi1Ml1wp0'Z2N52Ej142ET3TB2RG1o42=+2?w1#d25w14W38q2gZ1(I2HT34z31G2iG2tU1lt1m,3A)1vZ2MT2fl1:g2Jn2;a2*C28[23@2$D3Qr2D=2RN34N2!b2%P2',2fy3B,32v2#u2QI1}X1gT2!T36k23%1>$35s1+W2vL1t<2Jb2?u23]2v{1DF2n'3Ca1i@1z72>D2=02Ea1)22D!1=12To28(2T;26V2[c2aZ1>02T!3T!2Fe2wY2qm2n61c[2@j3IN35<2ON1?J12$3MF3DN2<]1G?1?j2^c2>p2gu2,X1'p3C92431)r2C^2(11HX3B*3An3Qa2VI2Cc2P033*2z'2c>1{O2cC2lg2lz1wn1<21$[12Z2x[16k2h@1&{3NO1Im2G/2>U1hB11D0%p1=%1NS2nx34S1O81=S1692[Z2dT1])2!P39u2$Y1:*2+c2jm31K2Fv1mm3HZ1tM38T3CY3GV2H22CD3PU3NJ1wz2282L!2h>13N1B%1iF2=l1$*1#W11R25k1tf2+k1ZE2BT29b2b>2/T2h01^11&,1t^2Sp1p31&G1vK2i82t%29@1U!3C61tj2V{25#2is10a2cv2212Rc1j21#}1QH31z2?H2As2Kn2oO2p&3A]1s/2(}2FT30r21v3RP2)31I22q(2N,2,f1v%2dJ1Y/2SE2h12+72RB1))3SF2Bs1{e2/o1wI2X]3Nb3K)2e>0u2fJ2i42Mn27?23b3FC27E2M]2Ba2w41]d1C03Rj2uV1he23F1Q1%#1(52/f1C]2sr29[2bA2<'1J;0,u1m821)2YQ17G1Lh2hS27o1;o3H83LZ2ax2981s{2eu1dr1Hj2(V2Oj0@c1!X1&z2(72Ae1>31Dc1fm2@'1o$2:#1vP3G*2]%2UN3A^3262EB2P82Xj15;3Pg1]c2=p1>51ar2I91%>2N22?B1cY2Be3PB2m&37j2G<2ES11F29t29D17+1'<1&u3Ex1rs2Xt1K82hc2ep2YZ1#J2TZ1R;2Ue23:1:/2Y*31o1Rc1eK1+F1t&0>d1O/25t1Zs2ub30^2BE2@R1}d2aH1$G28}2@l2C$00q2xA2gl2E40{+2BH2G=10K2Tz2'01,M2br2/21Lz0>%1]S2Zp0:q2UF2rm1Le17=1Nu0=v2W+2SG1&C2*'2Mq3D+1KG1WV2nS21227g0'/1Gf2LQ2/*2^w1BB2g71g=2e]1Ek2y^2MK3AW3Bd2sI1c<2^M3O22DQ22P2/337K1g11'Z1L@2@G1n?1&;1/}1+O2]M2tJ2Q/39J1$h2p[1bo3I!2+91W02AX2@d1zC1Y!1c/2j12!k2wu2ay2dj1:o2;O2RH1K)2;z3JQ1F72oz2q63O&2tC3J;1}<2)52!62Sl1t41X]2O3051WG2!I20C26W27C2S11Un2zJ2<937D1%Y1k32eb2al2KE1H?2a+1Wf0k83Ey2M01!h22c1{s1jN2##3EM1P<1,O1w{2yi31:1]p23w2Bh31[2V^1%62#h2Io1%l1,j1Uz2,,1zp3TD1yn2w918Y1$<2I<3C!2RA23u36]2e%1*:2OO2a#22{3Pf3Hu1Ka2LP1aA0'P1bO32h2[H2z/2341%F1Vu2Ix1Oo2V51ht1{/1Oa1>v3H]1@q1>3H}1u#2^u2p11x&1$P2!h1Ev2yL2s^1v62=u2[Q2dW1]]2nG2f}2r!37s2]G1a92(b1u%1zO0},30,2LT3J<2Fj25W2CT1QT2^92k)2XQ3C#2Uw1'22f42y(1k]1OO1;m30u36W2J*2%R1d^2sH2Xw2201;+2o91}M2Q%1DJ1Y21Mm1&K2A91+;3Ni2];3501)w2*W2$s1AQ2O>16E2W22<[11G3SE2M72)N0/G1[}3J231?0mE1gp17P2e&2EI2m]2X)2HJ3F61)a3Hk1jn1?R2PP25f2<233935832=2Fb10V1x;26E1n+3Ia2S92eA1}#1)m2m/3I?1,d1Lt2P}3I71!p1e=1w91?i2o?1{$1v41Z021s1HT3I21=;2'o2nq23V1s[2YD2*w20S3VB11*X1cw2)]2%[33h39K2YR2l^1G=3AN2&O2Um1(72i(0z/3EN2/@2vR3PC30o3YI2Nj2s?0'T25y2;Q0>^2Dh11M2hk32W0]%2nu2VZ33Z21p22[2xs1]g2]h2cx2Ql1bt1k>26g2A020m2LX1{<2nb36z1tB2Jh2G;2UR1nb2jl2yh1jX1701W}2qh1#y2b)2uh2fo1i$2NB1)t27x1Te2MC1A]2Xf2ME2gW2vK2aA2<,23s1,R1(i2=W2Km39w2dH3P+1KB1,I1I'3HD22&1z:1zg1zc3B21s)3E#2g,2U)2A31A42V:23+2}V1:S1VQ2)o2Ax3U)3Pc2W2UT1=w2H>3HR2VC0zN1b>2yP1FX19k1hG1D21dU2y82*#1Cr2e(11/1bD16c1;=2a:0'h0:o2u}2m%1Jh1{H1@70}01N71uX1i11aM1MH2ym2x73Ag2=*00s2e21Cg2$81nC2fD2cK2AP2rP28I1T82{e2vl2xq1*h2#U2xN2sh1zH2SC0>H2>N0l1g!0:=2[M1C/1''1M21Z'1Q=2cT2I{1Jv2lq14j2,r2eC1}N26C2QF1*{1Uc25b20Z3W+1vE2Wd2HS1dy1j11Y&2(:2/51sp1Z<2]Z2yd2aP2@J1EM1yN1+M2Cq3CE2z51,w2@]2:u1eA22*1vB1Aq1gf28V0'd3Ih2V!2:h37y1eP1Y:1I52P,1f,3Qv2!<2P^1dV2nP1g$2Gk2Y61pw2Z+2+]35Z2c,2:U2la1xk2b32oq2Se27(2lE1{n2xI1Wk1Ia2;P2=v1M83FH2>o2k42xj2x/10%2rz1N82mN23a1cv2N>2c'1qQ2I(2pp31^1OC31m3D}1^C2l83Nu2Jz2ll2sb3IB1R#2z{2tg24J2CU2ia2Ta1+n2={2Z42Bb29y2gK2u>2>'31324g2Kr1WL1FD2IN2x633m1by18%1O@1Gg0,<2*T1u421X=2yI2jN2?Q2Xo1*S1Gq2dz1f1Pn1p?1vz1tQ2yl1m)2k#1rF2j91jI2EC3Ac12ts1OA1Fy1OU1/J1&X36<1Ec1Tl1:z2r721D1*P2y>2o32v#2gj1L{2&l1,F2XL2w02fn2LL37$2}Z26n2S22wb2ps2i91rX2AI2(s2u{33k1kp2mv3192hp0;T0>,1sI1mC2Qs3122#T27:1g+3V522;2!A2CJ1Ti21d1d;29&2:P1Yl1Qg25&3MS2B$1gK3KX26Y1[F1'&2T[15v3R61u83G[1uM1y21d'1^Z1Xs2FV3ML19=2sp1Gb2[v1^82dN2'Y1Cc0,22)C2{:1p!3Q>25(2%C3JR1JC2)f1CF1u&2@r30!2l735N2^m2m=1(r1683MU1eg1%@0:}3D*1T91/L1k#1X;1X[2nz0(r2K31=@2[r2t+2?=2%'2^12's2q,2&{2B&2BU2{k2=U1;C1ZF2='2Ud1v#0{M2lU2T)1Zc28F1bq2,K3Dh2{{1=O3L*1&H1i#11#2+m1X%2mQ2}$1mN2772c22T61w#1Dy10P0*C3So2P?1Dz0!*2HW2372jx2],1]Q1tK29H2/L1?X1xI1B;1x=2h81(+2ln1p'1G32=k29<1(M1F%2@D2[(2W{16Q2;g2PR1@A2E635G2rT2DT1Vq1z}0}?1y)2E]2t[3142a>2?U1!u2i[1B+37)2aB24;27G2RC24H1PH1=82OD19L2Do2YN2Wr1p+2t<0:314U1W+1yZ00u2}j1#I1;P31V2Pt2702ZC2$32a?1df1,p1?#1'724P2HZ31$2B;1A^0{U0(N2C1E)2it2m+2{q3As21r30<1n$1;u1HF2oM2ow2^X20D2Ff3Jy24l1Ol2l52Bv1nz2v=2<@27a2/(3Sa1wr2i=2Hi2o;2+J1jh2w61yT2#B2bF1#[1#{2^42!l2!t2jL36b2c:33J0(p2833EV2,g3202uA2*[2?@2nc2rF3HK1BR1rR2{p2cz1Q)2Kj1K#1bH1@r34<0x'2eP25Q2ZO2s(28o28$2HK2f^2>K1@81nh2iQ29n2BX2j82&t1{f2,e3TX1g,3XO2X30#f2gB1c51j>2@?1$42=:1as1FM2)r1y:3Q{2Dk2dB2k12D[2:n1?(1v&2D723j2tO2%A1782[p1]<1)E2*Q2Mu2E:2v;1;}2n423c2wU2r]3CD1*}2Hu3CK2YI2Ci1tu1x}3P^2Ss2rD2HF1*M2A!2mt2/t1Lq28s2ch2jc1X91((26d1FF2^f3Cn2Y{34{1O72LH1su26J0;u3X62)j2uq1@Y0)d1Z#00w2/i1zj1y2:i36q00z1Xi1z=11,2g31pE2/!2Dp2[F2+s13@1!<2CS34F2ID1[21+S1:01OP2=$1'd18I1VW1wB1j&0&T2c)1lX2pM1Ww2>w2WR1v=27v1lS1wb1+33V228A0&n0#;1Ko3G61CN1(d23^1<@2v%3FL2Jo1N02uQ1QN1z(2D*2E)1+s14w2A)1J:2f'38!1*41%v2Y$1Iz1AD12+G2@i1LC2ff2@Y26!2m51?l2lY35f2Sq2d/1s}1X!2@}2a!2Oy1p{2]A1iI1J{1nl2EF1&l19(1892NV1n%0:r18K12,2b*14>1,]22/2^y1iO1Gh2aK1WF1(o2rd2$@1v'1=(2/K3HV1=l1bP2L>2'q13x2Lf2c12:s2tA2}y1sN2@U0:Q2$Z26f3FZ2b^2ix1Q'2v81:m1,22C*37+36M1@y1lx2N2Ll1hI19q11*3A/3J:0ob2Fl23Y1gZ1U22E/1t713&1[&2tb2dq1;k1d52Yn16H2{I0wU1:e1C{1T:1a;0:i2y02BS0;K0]>1lQ1FN1V]29P2](1cQ0{I2fQ38m1IN27i2962US1>{2=F2t$1jM2]$1a71!71s&1Xe3Jc1&M27W2FB2O)2r=1qO2z)1Cj22$1h52g81ME1:O1;Y1s41}U2152R41p$1p72[s1$714e1v*2}91+k1U=0@,1Df0'a16&1!42c52%D1Vr29l1o?2Bk2,A2nk2l}1u21B(2{b0]^2g>2DV1I,1e32oE2+;2[L2!#1Bj2)@2B}1mb2mA1rD2vz3M*11X2''1@j2sq1$61b(1#o1<62Gu27m1uh1Mj1!e2aU2xB1&/2W}2pa1(s2ND2T42FC3L}2oS2:C0*b2F!2n]34d3DS1<91vn2Ke1FG1sw16Y1[r2850{d1{m1;G2061{W1,%1=[1FC1r^19D2$21Hh3Ak2$^2Vm0k22&$1mZ2S>1US2;h1&L0;w2Bd13'2)/0zo1jo15X2#p2ea1UF1{?2:J1yG1wQ2Ck0}a1l818Z2$C1cI1j51',1*I2Z%2di2oF24+2+*2QO1b72}22cl22D2&n1bY2M11/=1x!2bk1'm2Lg2Eb2?E3Kl3Kp0[61w22ve2}S3Jg1Ta1;N20,2SL2Wu2?50='22p1sm33y0y21/q0od1ph28E1?Y2q%1ZN1Q!0[P2dy2i71kq1hk1}v2g51TE1^@1mz28O21Mn3Bq2Dg0^c0(H1vb1g;0us15m2422@>2a41yS2Ij13H1Jm27q1Lc2a'2m62%L1hp1{C1NB2l=2F?22l0x#1yu2F210;2Aw0!F2i+1VF1'u2Kt0jb0(X1Ro1251,/0#&1zF1fz34j2Fp1eQ22>38I1uR1wY1G[2)G1m;1l]1+m1q,1xE1g)2rp1Xb1cW2{$14;1sy2P>1xO1$W1uG2YE2jZ1qX1[v1F,2RS1*L0(Y2WZ0wc1{I1!w2JH1{y17l2PF0@S1k!1U,1Y[1yD17'1JO2lN33S26N1SN1gu0yE0m;2#!1h!2!}0#]3Q@1'Y25U2rI1WC1%M0*l1:71Kq0'614,1aB1DX1hV29R0]70,(2SV1Dj1[91WN2pk2{Q2Lo1!)1231]B2Bz1L}2Gc1r;0zV2#637'1TG31S20v1#>1[D2EW1Dr1d}1q(2UI1m32A:1U11xm2nM0]I1If0<#1iC1Vj1tP1un1:E1/]16r1<72q72e,1LO1@l1d+1Eg1Tv14A1XI1&=1mV1Sz3603C(1px1js22E1X:1XF1c>0>11bk1ps18q1OJ2q'1e21Q01>M2vb1kG1!%1]^2CK2mH1+P2mw1L21l>1{k2fk1J^3TY1z]0<^2C(1Bo0=!1l{31W0/#25,2z;20V2It2Fd2(P2ER1,,2Ir2Qq2Ha31'2Xr1ep2En2;'2ny2MO2Qm23*38}2B#2l;29)1iX0@P1P52@^1sR1rq2&q1=Q1{'1#Q2Lq2Yq2Hw3971+62Q[20F1L3Bt1w$0}30*r1=]1Mv2+z3J72kZ29q0xv1kQ2bO1(L11a17u0:K1a@3Mv2*536}1zV1G01}31q81ke2E#29$1j%1'!2oV2hA2FQ1,32g11lF1z*0%u1G%2Cy2(F2Y42yt2#>2vS2wd1h30'b22Q1R&0:T1OD0<10^f1es2cR0{:1gU1:r1w*1(&2{'1L/1,80&G1wU0[j2Wk1S@1LD1J/1*b1Ud0{Y2Qc0)>1NC0>u1JA1P>2$!2'm1nX1){2ms2&R1/S1N91h+2gf1?[2S2/z1aP2so1%B1!]2eB00/1)F1Dp3Ii2'h3Lv10,2yn2}g1xG2!81S[1By1Sj2U?1T32Jr2da0+n2V71991CH1v}2zw2lA23L2}T1@o37B1So2g*2481Lw2C!1Ho0>/0&Q2Gp1?526(2w)2D<2Wj1&t1L50;)23/2>g2::26e1nn2{i2Na1uH2*&1^=1WA2{n1eC2%!2(n1WT1yR2Kg1@51zY2vN1AK35p22u2eV1di1@N1!>1:I1eh2QJ1(k2Q<25o2Wz2fp1i71gH2^;2e/2W/1*]1^I1n^2X#3Gw1B{3B12?]3La3AC2qR1pz2;631Q2Y/3Hg1wu0:M2+51i^0s61JV18c2lV28B2P#2Eh24F1UU31U30Y1zx2AL1R}1Yg2K,1rU1&)1?739W2]^2/e10R0{$2g$1Z}1%N3Ah1n{2tf2QD2)92'W3I>1sB3O,0&X2Ns2BY2Qu1,?24&2!32M82km2a%13>24V1k12J01];2P32bv2Zg2Ut0&b2R?2Ge2NK2}A3J{1j!2ib1602$i2A81$d2fG2[R1#12m01vi1541B72sV3FN1q}1iV1xb18M1(O3LQ15^1y93MR3IM2r80q$2M&29Y1Xj1Cl1e80:$2hF1%h2l]2/}1vD2?Y32p0:/1'O1Ug1O$0z{2a621&y1c]1{T1r81p<2V*1Q31/<1yp1O%1qe1;b0;l0)+1m}27r21u1eR1E[1a]1t51I11JR1)G1(n1'>24b1KK22V1i60>E13V1AL1hb0:R2R)0zp1!50#O2xG2142H316s2&f35a2l,2rq28K1%32d<2%r1(?21F1>81[A2IF1Y82>[18k1B:0+O18(3622GD2eq2<>1J+2Z21&g2A{1{(2#^1@&2jo1+G2Ga0[p3F>1i:0wo3E11U(0(B37P3A'2Gn2bU0p!2H%1R$0}@2cb2@42^v2902A@1*1sW1I=1l=1LE1?C2bM1@p39p1@U1&h1tE1k/1:@1A$2VR2XN1rj2O/1$%2Z,0)]2H:2!c1U<0}j2Hm1Dk1qh2tT1/r1x%2g92%k2Vj2MW2R[2r02bQ0&@2nW2i61WH1Fc1d@1C}2T,17>1c61V*2S[1ri1351HY1/%1Gp18D2h+2$&1am3H61b81oH2KF1f00'*14o2$B1q41q<1eO1Tx1t#18;00+1Bu1,}0=311W1IP0[u2Zu2=?2fL1Kk1z32Oa2o%26F1]t2xi1pZ2wl24Q2i;1{Y2Rh1N+32[2TQ2?D1H&1[V1<}2]92RT2OU1!*1&B1(82r[1rv2iZ1mG19s2wR2K22lu1EV1Ar2BB2(91{83Ev2;U0=g1(Z0yn1ce1/v2A*2H'1XO1qa1d!25;1wd0=U1IL2TL0*+1wG2$%2lF0@w3Du2kH1eD17{0;32DK35&2X53N)2g01(}1uv2kN1;12D%39=2Mt2he1rS1AV0=n1V41N@1E51Tj18f2P(1z%12X20o1)x0t#0kF1N53J^1M)2qc26x1L3HT2by0!v2Wl1yI0+o12f2YG17m2*:2ky2i50z+1]H2&b1SI1Y*0i[1BM1#x0d53G!3Hq0[12%)2Ru1{A2kw1^02&m1B&1/D2na27n2DF1hu2KX1Pr1OK2A'2n=1/N1o11P81y>1q$2f?1JB1e;1o%22v1h{2B/1DL1f)1v024)1Dt20$2fB2jn1%L0w,1/t33w0wd1VU1101Bk2oh2M;2sS2292e@1>z0s<1os2$72#*2ZX2m>2sm3FF2130%31rJ1dm2Ii1l)1C;2zp15w1%J2JF1VV36C27}0zT1aH25r2IO2Yk1WE2ij3E=3JV2{71h61=d2d}2Nw0;%16i2793PE2J^2qO1+82($1d62cO0vZ1YF2t(2N=1IR31>1?a22W2Y32{A23R2vY2!u2NR2I*1fO1!j0<81Fh1r/3QX25G0sM1uC0,>2N<2R&25a0%q1nt2JO1:]15#2f*2%X1Jk1r<2eh2jh1**2Pv12?0{)141fK1w01lP2{R1S$14l2d+0nY24?2@91(x0=z1p%2T?2o}23<1w61Qw0}f2hC2v!1Z60+L10$1xB1;q0!>2]+1jt1()1=020c15G13K1uS1;W2820gj2)R1]Y1T}1*m2KP0[A1fH1:>1*H2&311b1po1Z70(72}k1BN1261+51ES26{1RS2Kp2Uk1==21yQ2dU2gA1mi1(R26L23S2X*0<$2io25>1EI17)29U1?z0rl1t21ej3MW3DD2{]1ic1wV10c2WW0,Q2aw2cy1XG2%V0}41G!1fC0[e2*X0(31^m2T21lu1b^2C81412sa2$+1o718721,1n51D40jB1h?2%41ZB0:g14(1ds1q@2G)1a(1$D2]72J!2(^1&Q1bj0za1MU1zm1(j1y628'2LO0z61E@2tj0pZ0xL1ow0);21k1]h2kB1+^2{!2wn2?m2XZ18G2b/1661m71Nx31i1Uw1:F2w:1it1u;2FJ2ZU10=3A,2MY2Qx2F)1]w3V[1*10;[0Y]16e2K[00=29g2^{2+/28<1Rw1ss1St1x31,112w2rJ18J1cX32N1s;00!1#]2d%0[b2@q0}G1>P1ci2F:1F?2NH1/C1S'1H*2P<2732PH1#^1vm1dw1m?1H=2w%0su2t@2!p1$S1d,0]1sY1r11jU2%y38%1H/2;y3M%0oo1>F35$2=#1!P2,l20%2JD1w+1nJ28Q2G&2E!1;)2mU10m2?T0:Z2v@1^X2Ti0)Z1d21f}2Tj1OX0?n2ai30f2c=2vs1vL1M72bG2gs3P#20p2KZ1yL0:^2JY1)e2Cl1P(0?U2Q^2'j2@M1El1K%19x0[$1uT2W$2if1PW2{B1iv2Z/3K&1qt2UL1}=1[g1TK2bH18o2g'1xD2g=2Pe1d<2Rk2tV1(/1Gr2wT37e16R1l12022K@2I71y52W(1y;1dQ2F13LK2PI1x)1/f1@k1=)1KP1=K34@3Kf2Kc2zc2d(2c;1Nl0/}28^2dR1fu1WP0]g12v2g+2u52uc1iL2xZ2L]2081@B15S1%x2Jy1{91LK1Dq1*)20f1q&1*O2DX1Q[2r62#v1hA12>021U2l%36#2qP2+u1X^1>q2>)2*l2]?2xU1#31yg1sk1')2H^0rZ2!&0(Q3QL1<:1&S23r23o1371In1#f2$)1*s21V1FQ2q)2qN0nc2{F2[)2te1#z2]y27j19h1f61;/1L(2bx1%u3Aw1*91IH1hq1aE1L62E?2vj1;n1)y1!02MS3Rq1[[1c@1r&1y*1^F1bK0>U20T1Fr1{w2Q42D92g:2]O1643B42C[1;@1b51'@1K[1c=2#t1>#0&11YI1S,2j41bB1Ob0[E1fd1bA2:627/00@2B?1>K0/k1H!1M*2V?1>!1Fa2GQ2]q1l&2nE2r?024a0wT2Uq0(C2A41aT1#!1[31AB2U>0:e1Tk1ku1lh2:12K/0(d1,>2F71IT1K62Co2%O11g1uA1gG1#(2,I1io1=U1SJ1pq1hm1Yu1dz1yr0qT0&:1sq2@%1QG1Kg2WX1S%1@i1,51,K2mY1)?1iW35D1Ub2aL1]91282mm1R11Ja1oD2BF1}i39(2p71@H1NM1pt0o&1k21oj1r:26G2NG2/R2Ai1;?23T2%62@01GD0^v1U@1gI1eB2jU2nN2f:1tF1@W2;01Uk1lT1Q21L72W71uI0kC1}11Z51&U1Jz1qo1wJ1y[0}H1ge1uo2G}1NA3O]1D&2Vg1$e1MO1!J1a30<%0*D1kn0eE21y2U32Ca1kM1's2[i1(@30>1b)0[m1z42=j1do21Y2Sx2@82Rs1w41}61Lo2ZM22o1gc2;21}41,G1<421l1gr2D(1da0,Y2@$2u;1fY1^v1r?1W&1Z!1/T1r(1lf2[z1pr1$n2sW2Xk2L61vQ0u@1H'1G*1if0;V17C2uC2GJ2P%1i>27)1e41vp0)=19T1NW2]o1{30]=1F:1*x2V>3UK2+'2cu17n0xQ2rE15+1f&0=L1Vt1T722R2aj2QH2{42!w1?11k:1zu3T}1Z/2;V1(c2wP1Wa2/I3C52F32o#3JG0;e1vx1Rt2Cd1yq26m1gJ24/2LI1Qd1l;1g?1]*3E{2;I30x2L02jz2}J1yF1^(1tg1C40n#1C!2K:29}19r1pT29X0z00'82ao1fq1[m17r1%e14*38h28R0[!1i31mB2d51&b1]T1wH2jS38R1Gj1Yw2E;1f;2vP1:}25?1{U2J}1h>20?3Gq0tm2d*0!+1q^1DO20Q2^E2'c1xp2ii2c$1RB17W2l&1;K1='14X2Of1S60/l2lT2A61kI1c!2o'1y}2wO1d>2vD2E92u'1?K0&91mE1#H1z{1!D1ba2951dC2X}1TM2vy2O&3L+2&522N1]s2?X0pz2T%1=W1O^1=C1ju2T/2hz3CI2*e2#%1*(2hT1(:1MI1f?2/d1$T1pn1Bp1^N1BA1k42>Y1%<2Br1+'1ua2AB0x,1&R2s>1nH2t91fU2w#27&3Cc0>p1!q2Ev2GR1d%1n,2i!1W#1'x0'%22#1Zo2,J1D/16W2?d1Qi0s30xX2^80zZ1$O1?k1#t0%J2um1&O1fL1U%2IY1/>2xH1!i11Qa0=F1mo1=!1+12d'38?0>S1=g1b'15y1B}2m^1!f1sl1k'0x)0$x1:Z1yJ1Q]2LA28q1V02fw1,T1VD1(D0#01me1&P38O2LN0?/1On1h$2?035b0vz1c$0:x1$^1/62lo2$E1T<1Vc1ca2u<2Sj28+2p?1o{0tN1Cw2Hg2(z2881o[2[^18&21O1Jb21T1S<18H1xz2G02S;0v<1P$1R'1B21ax0(+1=f0$E12<2QC1l92+A1CI0s%1Zf1#u0,v1cH1j41U[0uO1I[31x1ch0^@0]J1G91%,2gI33H2GK1Hi2H;3A[2;k19/1tC1MK0:51V(16p1Ty1PI1F32y]28C1,L2WK2I31F11t>12S14/0w{1t*1ou2lD1>&0)R1;p1r}24C0?o10:1sJ2Mv2Oh2eo0#*0uE2381o>0r>0;R1C70/C1u{1/W1@91I@15$0(u1G(1o+1oS1oF2N#2=229=27=1dG2I61T51SA1$:38#2=e1ID1Io2!^1rE2n$1Vd1oY1!n2ab2#30;Z2B83P=2l92fz2YJ2F(27N23g1V+2661ry1A%2]X2Vo1:=12E2{Z2SA2L=27@2D;1sF2#$2MD2Vi0yb2E%1R217!01vg2p43C}1qG33C1201Oy2s&2yE2%N2DY1{R15V2oX1jV1Vz1XY1W=2Am1n00,w16/1562Eu1hy2:Y2uX0qb1d:1pu0{O1gR2}r1=a22!16q1X@1Nm1v^1;]1[:1Jf1L^1j'2zL0:k24,1'03Y91C(1C?38L2nY1h%1g71Fv2)K1:!2sX35F1dx3T:0#z15)1l50z)15l1Hu13l1Ke1tw2+[3GG1Y?0$D2w>1OB1ZP2d11s92v92$h2Y]2101Af2RL0(z1Wz00$2ux2F'2Ie2DN1,B2{}1&91RC1Mo1{c2GA1m[2Tc13b2ob2g&10G2c/1ET3161ZG1&D0*91P%2Th2I21d=3D000%1^)12b1H>1H82l41ko24}11t27%1MJ1m%2Y=1$w2=X1pB26u1[;2!v21?32S2%U2^<2>s1RP2e61v(2z%1Xt1B016=23P1V%1UC1e:0$Z1:M3M51q#2OB1R]2J?1I*0&{3BL1380l^0%c0}D1k)2hx1y02ha1Bd35/1A:0xY0%g1m(0!h2a*1b]14I1eU14?34'1k@28D0W81eG2MU1QX0?h2$61i/1Od16)2o22^N0^y0#J2!?2NJ2Lm2O,0%(2RJ2S@1WO1]F2@a1J31O)1gy1v{1af0/u0!c1W'00^2^}27F2ZA1?0,'0>r0>[1Xg2pe1(A1F(0t/2&B1!S24[2Tp2F#3G}21+1%X1o21iR1m62+^0o32u01Fn2'%0)i2Yt1oc1o61TA3Vp1S^1Vf1L31n@2Q31w)24q1n&2s}3XC2G>1u=1fA1$l2#P2qi1ji15D1jK1(p1zb2Ui0>71zn1>D39%1A'26k1u*1bp0'K2/j1tN1p}2]}1{L1M+3A92xc1J!1LM2=r0/K1iN1)51uu2jC2b72(2/g2';2g%3VD0wD1s%2443DU2ZR13y1!;0;x28P2n,1f21^E1e%1hc2H42{f2BV1,90$U1@x0('1uw1?m0:s1N40'91cs0+t1u(2zv0$*18d00*31']2dZ1lE27Q2&P1gV2241(]32A1^J2gF1?$19t1b$0}v1xc2u&1BK0uS2SZ0y?2=P0w}32K1(P0]L1S>1h113I15134v2W@1r]1&a37a1402#,2P*16D1CS1S929c0[t2;N2EM1)o1&c1m91k92Re2SD0s)0}z2L$1XB20l1I/2Mb2F*2PC1:22XV2C+0%81sK21%0'V2fH1Q*0}Y11+2K^1[p1{r1;B1Fi1/+1ag1#n2Yl0?523M2hl3S42Gr2OM2YS2R}0]+2ZL1qd0}k2hj1j@2QN1qR2&[24S2h=1RL2,o28r1nM2es1<82Oi3F}2u63Mg0){1^G1H;3Jm0nj2e42kt2!N1T/2?g0,,0:E2J61Sf2j@2va2IS19^1LY16Z2]=1P!1!a1YV2,]3C^2692=q2Xh2OL2781=q2Ve2[N2qK1W]0}[1%71(=2KM1392KU2(20XI2632'i1Xl1'a13$2q:1v92tm2e11wf1'81o;2Xq1E=1Xh2#l1?h1E62B%1=A2nL2XT2S'2o82Ma2{W1kw1b33W)2GS2xS0>22>;20[2l32U,1>%1!I1431ra30w1XM00&3751?02SM2<71]=1SU1oX3NR1hS0x$0p+2UD1{h14y0!d39E1r$1=x2B'2hh2?>2Q01Wi2=Y1W10}V1rA1eb2ip1oq15o0po3EG2Pg1:l2Jp1qH0@a1;!1H42j;1^Y1$,1Tn11y2$=2q=0tS1n41cx2@#2f[2{S1U^1C@2Rj1Hp1d&0rq2l/1P)1f+2:x2Xb1ee1Sb1fs2VF2QP2A(1GF2*<1]{1XZ0U[26o2z&18L1IY1Ak0#C2UG2Ju1P]1;M0]X1Me2t,1Ca1xX1l32cA2(m1=t0ri0xH1tZ1kO0[N0;61gn2UH0k&1g231+1SK1#h1T11IM2VH1Qn1$f1{g36)3P01Zv2zg1br29{0!m0)<1dZ1J(2'^27]1fa1k&1$01m42Kx1cR1bl1A91v71*B2:A1D52N02]<1$'2ee2IZ1eV1:11o]1ff1hE2R52?S1wN1X<1iw1>I0x]1uk1DA0+]2+)2I^2Yp0x>1Py1@I1DU0&!1<#1y'2AT3Jn1g%0+,1>Q2T+2X<22Y2Y[1TN0#/1K@0+V0]A0*=1o=25p0}r2h%1L&28)30s3A127A2)S0''19o1aX1b+14R0wp1Zg1lY1eq1QA2xf2O11L'12F1D;1cu0$n2m;1531SX1T+1JD0!V1sG1ZY2R#23Q2fI31r3L20qf1C:2Nf10y2H/1ab1sM1X>1d$1JI1g@1P#10U0bk3450sn2bV2FY1@21CV2cd2YO1Vy2Z)2*u1!A0&R3T82jd1,y0xr2[O1yK2C)1$Y2]C2E=0@(25]1Q134U1@Q2Kk1pW1E31U:2xY2)h2G!2=K2qF1O<2hs2q;1TP2#b1B@1zk1U#0j01Q>1#S2Ho2WQ2Jv1z035B1=?2M?2Zz2r^0g?0eA38K2K$2%W2Vr2X'1*80fg12n1R@3G(0a40yG0?C1;d1YJ2n)2ev3KO1>i1}21Pu1;w2Bu0,+0[%0z'1/:2ua2Va2;b2S&19821/2$P39v25:2T82f$2Ip2Kz1IW1$E1s$0k]1{x1G,22)1z#0wq0^62Ao2&j2w<1<;2?V2AR1hn1]U2@B11&2Y}1+Z1HW1G]1+@1Da0kr1r@1C#1ot0?i0[71Rd1LJ30$2vu1E^35u2V'2X/10#2fr2Z{26Q12>2,U19b1E'2O]2VB2+,0>O2N40SU11H2Ad0v/1hR1)2L;1}R1aq20r2MV35T1UI12A1w}1Sm1Vv2!V3JB0@Q3Lh1u^1z/1,q0vg1lW1EA0'$2U=27z0,y1vt2cm0,a1@+2xp2n70(a0]H1yd1Iv39T2{d1F}2HB1#R2jP2k:2b;2P50/b0&52st1t+12(1N}1b{0{x2&x2pH1yo2Iw1Hs21#2({0)w29>1vo1N>2(l1Ib1J)1?U2+210z1Re10^0&C0^L2K}0;G1Z,13611J1VH22l(1Z41J03Pi1QL1j#1tI1}E1Vk1/!21C36J1w82#}2rL1w72hm0%K1w,1gq3071Ou2zr2N(2G32aW2Yv2Pn1%H2SO1fv1(12Nh3M42b@38o01&w1^{1nT24T1iY1:q1Sw27;2Nz1yw1iP0[R1$=2bC1hw0&c1$(1*w0;y1H02H91)W2c71,)236i2V02k,00*1jb2rh2tW14)2v]0k'3KK2X423=2^,02Ef1,k1KH1x21i=1(!2e!27h1o00'Y1g32<01Z%1$q0>+1M%1#:2g{1Q;11;1^O2QU2%H1>Y1A+2$r2&*3Fu1cc1fZ1rB3Qb2q>2%:0b*1Ee0>&13g1I61v21+!1P[1Kj3250Y#0h*0u:1M?1V53Vg2601KC2zK20)2TD1N;2!,23B2[k0}d2#L2s{1?c1ww2D,2G*2>x0(R2pT2/Y1@[0[n1qv1;L1cL25+1R<2<$1C+2oI1RU1'L1jj2Jd25E2d61X&1XJ2=R1oR2Z]0lr0?<2;p2t^1C)0!71qf0/n2i22Sk1q>1fe1q;0mt2Ul2w?0];0{y1{q1T,3RF1'P1j$1$J1}{1[%2Ld0%z1h^2SQ1k+2AY2BM2ql1*g14&1*51n!0oQ0)z1e72LR2G'3Gt17&2Fs2D82FZ1r41so1BW0##0q,2001+D2L?2KS0>{1cC1xL1}P0yC1Z&1[x2Ka0lF1CW0xg1111&23Ru2zD1GB1YW1Wq10{1Bq1r61;<1Z+1}H0,725'0+^1}F1zt1qE1tk2Rb1WX1z50{(0,z0@42;Y1b!1pa1ms0^#0*!16l1+>1O01IO38A2(p1yc1E?1N{1MT1T22LG0z$2*p1^P2a;0oL0(e1J'0(s2A&2z!0*c1kR0zD1p#2Z*1f=1zL2B@1ap0=X0r^13C2uf1kU2vp1x@2c&2X810*081&A1#$0gf1wD26;2sc0rY2R!1ZS0z=0tn0!60[<0xO0%a1';1t/1UL1Ic2cw1YD1lC2IB1Aa1=u0$A2sR1)n1'31z@2M%1@>2X:21t31l18,20/2B[0'W2cN1o31o(1iM2c31>h1*?2S:1g(2D61[j2A}1tU0t+1r>1?32;40@[1sa1+l1om1tL1FJ0qO2^J3EL0'[1vV0$a1qT1]72Fq1[41[<0%k1Hk2qC1q%1KN1pI1TY29]2}b1Vw29!1V<2%e1F'1SD0[21#k2QM2bD0^[0Zg0s^0{r2nZ2N612121j0/D2PV2Yx2r21!/1hX27X1v10{!2Sv0:[0{}0,S1G20[Q2l'2m?1vR1E#1GT0*x1P?1rk2H+0!Y10S1o#1Xp25z2z23A#1Xz1nc1TL0+h1)70,h2!/2Bi16^1]C1n82iu1xw1Ul15O0%I1HD1;J190ey2zE1#%1XC2'C2Ys2GW1b?1(C2nJ1gj2HP1R:2':2$a2NM2wE0}B2C61gQ2?K1V,2Z@1D(2nX2*^2d@28=2Dx1'{2jI14z1^i1Yd0{o3RT1Ts2JK1Yx14p0'i1C'17Z1O[1EJ2Y+1}k1FS2@h0&I1M(1RX26q1S!2gX0l60?R0[^22^2Qw1u!2do1EP0>=0=h2V+2pL1mR2z70;730j2>/1H31u30:72LD2d>0wa0=r1h]0*Z29(2b61^/2F$10e1=}0qp0$(0/F0*,24'0)P1Wx1uB1Bf2d=1!o0}M1t%0$G16P1gA1oZ1D$1hQ1j;0t^1UP0%90m81'=0}Z0k<0*#36H2y@2>Z1eH0,31Pm0pQ3L?2uB2273O*2U}0w60q62cr1K^1//0d;2u(0s=0wy1V>1e@2K)29G2111+j1A32k+1;02N/0]T00)2H(1rY11K2bd1W20!(2I!1^S1Y915H1}Q1e{1)L1;h1>0^D2HD2$#16<2Cj1'F1rI26)1l!0wF2;+1Y}1,g1r21(;10+1,^1#/2ce1>s2M,1)Z1=/0W1$F2gc13}1+20%01>}1nx1Uo1S#2F]3U%2/m1f:2IP2Fm1PK2kT21K2$,1+T1Kb0nA0vb2/63MH0:V1Hq3BP1fi0+b1!,1}V0>X1xa1Ns1Ax1^+2ga1)Q0v=2mg2Bl1<^1RM1r70iG1:b2(K2j512e2?n2Ts1R,2p81Dv10T1wP17;1kT00q@2em1P^1340p;1%s1mv29'2S+2V,1#E2yR2Zi2IJ2x31E&1Dn1ui1pl0ev2p@2X62HH2v23020bz2Y80:N2p02#Z2)!2N;2vQ1e]1Qq0)&0@{1Uu10D1:+2WC2j'3V!1oL2NZ1wL2!O2!g0/M2ru2V20x}1c12HO1B82Up1Fw2LB1/p16N1+v2h70:41LR2/q2[q1hW2'p2hJ1EE2{,2eD0Zq1=Z1Fo1fw0{<1$12F62Iu2p(2rV1+&2W915e0eY00[0XS2,;1(321<2CH26j2u[1bn2Sw0Xl1Mz2(t0{s2wf2VS12G1x<2(U2W#19$0J2E12%q1:60z^1Y70nX1O10kt00{0eB0vD0ho1pF2Us1tr0a01Pv2Q625}1cy0@52K<1U60(=2@f1c;1^A1oI2Z12Nv2qA1!'0y91OL2[<1lq36p1bE1e1jY0]80;c1SW1Ri2M'1{*0@}2>W2T'0[g1ML1nK1+#0!)1iq1I]2d[2=B2LZ1KD2q&0,F2uK1hJ21;2KQ2dX0%[0u[1/@2cG2#i2o01121rr0)p1B'0Y1YM2p)2S524D1&I2dg1e02wH1}T2(r1nk2KJ1Ao1Ew2q?1:X27J2P!10o1Hc1E:11T1*C2Tx2QZ2E}2[21n11VX2452ei21m0/<0$P11h1091YA1cZ3R72N&3G81qs1s#2cF1CU1Pz2M^0=o1Y10o)2XX14612a2Pa2af33T1+[0jQ0*72040)42Jm1e*2sE1hY1N?1[K17a21c2*72Nb2sM32/3V61d]1XV0@h1EC1SL0m%2cs1c:0@:14s28w1U30lT1][1p&1Gx1kf1}C1m23Hr2oK1J&1Um1%02RU2q31MC2ed1:f35*29C1'I20w1!81BS1*a2Lz1?91LU2dP1ZV1Qz1z813X2){0%m1u91Iw0{T0qG0=+2i/2W[2O;1G)1ub15,2HG2Ec0r01f11^c23X15<1J}0f10[C2i'2+12OA2dr0(D1kL2T921R1Kr1(G2{r0xh26:1Li2Iy1D%18x2&a2qb1HH1kJ2;u1#T0}w1?,15n1jL1?S0>M0;z0s80$)0E1OH17^0wB2qJ1+,2WY1L422s1Y@1Es1MF1PR1C80,n1130}'0wG1O,1851Pc0k,1s61IE0}>17313K624k1u11R+1c}1q313e0cb0oh0xG2*]1YR2p90(00$@3Hm0vE16#0iD0mZ2P]14t1zU1oi1p*1]R1m*1tx1o'1}s1'/29#2aJ25d0cr0zE2r<0(K2j{1#51n*2d22Uj2A50gQ2Os1o&1lU1P:2W,1zf1N127I0lt1w;1Yy2Iv1V=1rx1!@2xn1*Q2Dn1=E1Zk2h22K41r*2!!1Sn2PN2Mh27D1(T2cV2[V0,020s2D$1Fs2BD1fR1V{2b]1mO1RF2Y;2tK1':0/v23!3Is2ws3F82L31Rl0):1kr0R]2$f1=e1iD26$2762i^1WY1kN0x029:2[Y30:1W*1zI1P02]40%i0+r0u=2Zr0!n0kT17$24=1'91h'23W1c32b51xT1^918R1q13L51!N37d28#3L(2?A2#r1g:2!a1SE2]'1(u00<1It2*x0])1Gs2O}030b#0aM1i+1tO1D918U1#p23Z2fN1A?1QQ2G%1$K3V,2E^0z12u#2?r2pz1kP1Ez0^s0gV0oW12@1NN1P42ww0{^1X70d[1D]0%P1md2[00TX0uF24n1S]1Mf1k72%x3TO1qb1&116F2ht1UO0gD19d1Il2QK0[*2RW0+x0>j2th2jf1DV1IS0y&2>e1YG1ah37O1QY1Nr0gk12j0'w13D1)j0;>3Aj1k{0!i1ln1(q1BV0yP1IQ1Fz1$Q2H&2Yj1+y0tO24Y1Jx2vF1I30pR1Pi33G1>10p?1G@3G910@1Kn1{>1:A1Dx1iZ22F2C,1c00%n2Di1}J1dR0'11gB0p{0(f1yY0d:15q0/w1qU1!Y1D{2pC2pK0}]2db0:<2KR0(q1^;23,1'r1x01mJ2cn1NI0%12T@17s2Z'0mk1/d1qi2dv3OQ1yW1xh1tV1#61kX0{00!!0>s1dW1;(0bM0&<1qI2bT1lk1'W1U)1,+2VQ1E{1N20t*1l:1W%2wW2s/1GU0$$1K(2*i1IX1ol15T2F}1Po0lC2mV1Pf1vl1*z2YH1'R2PD1S{2E015J0{/0'70[Y1F>1*u2+?1Oi1Rs0!o0x[0Ta0bV2*m1zw1k,1Ru0az1YZ0n[18V0l(0!t1F@1I<3G=0%j0::1T)2Z}1IA1dA1[a12q0'A1nN0'{2Jc3D33F'3Da2JU0;^1K<0rt1!2FD2+R0:A20d2Un1z22Fz2T52Wy1%j2XB0?)1+p1>X3W[1Of1I70ew0nT16a0$[0sW1*%0lg2Po2YT18W0(w1P&15%0r;0ta1(a1:K00>1^w1{F1K70'^2k;1+Q2^@2Ce1n1JF1/k2@s0^d1Y^1kA0}W2GZ1+h0z&0Yc1zA0>a13^10w2Wa1:p1QC1id1(<12P1@J1(60(!0vo1kk2S(0$r0wH0e>1'c1HN2BG1g514f1vc2i*1g[2W)1+91PL1W81fp2BC0wn2j=2@*2Qo24m1t'0>I00;1fb2321qL1Mg2R32Zw2>$0$c22d2ie2N81)>1lI1JG1Yq2]g1F*1N(0[v2d{1{)1d#1jy1>o1x>2C}2CE0@]2B:2$x1Z11^61oA1A,2/N2,B2PG1ac1}g2jk2AW1(^1CR0$}1k(1Rv2MA1=h1wg3Di2kD2=(14@1Fx1]l0zw0!<1[b2F,0=91hh1*T0's2g#1Ua2Yg1dK1cb2]63XM2YX1xx2oT1Rz1EY0>m1'12^k25n2!e1tJ2Ex3A;1G^0@E1bI2Q+2E'2yN1ij2Y12HY1S12]m0rT14C0r411U2932l00zd1+*11r1u}0=&1jc1gC3Ly25%37r1j:3S[1cT1K/1d32I&2b!1pA16S2pu2%w1[L1tl1L?2pi0y,2:40@?0'@1g*1)C0=Y0j;2N}2Sy1H518*1}c1XL1,D1Ph2wI2hH1l71SO2Mz1Nb1a02Dr1nj1]@1zE1/E0?^28i0n]1Ky20+1:L29Z3Hj1]o25=2{Y1#e2md1}01lR2$w0[:1r#18<25Z1U$0/)2#o27b0}t0r816I1QW1w&2l214J0=@2am1pM1P21e,1hz1/81?W1$52KI2[,2js1P122(2dw2b%03Ae1t[2JE1yx1J[0}l11{0&(1Tp1Et25A1D<0w10)x2F^1/)27p2bE1D@1nR1XP1yf0sU0wv1$v0lD10Y0b<1g<2x}2ja16:1^z2cL1n61)g2V(1*W1BC1pL1ga0wY2Jw0{J1Pq2=E1Tz25<13+2%K2J>1/?1?M29z2!Z1Z>2PJ2MX0zF1!?0>F1H]0}#0'R2PA0h21&@1,62fT2(q1bR2^q0:W1Ld2f)1HZ0@C1&61DI1mK2U'2pw2L)1LH3Be1(v0>31AE1cS0[31Uy1+C2f,37t37/1CP1R41yj2%S1)@1GQ1st2Nn12y0eS0jU1Qb0fx1Mk1H^1]A18!26v1BZ0+P1#21GO0}T1qS1Qr1>b1nm2yF0>}0}70;;1ox1e(2c+1U]1+U1Ih2i32T<0Os1AF2Py0u]1s+2Bx2:21FU1Wn0(%24>1ho1dO2mB1=B0cl0>k1]m1jH1Lj0jz0q21{42n{0g71P}1/*29M18u20'2iS2n21@L15K33'1/'1x#2Zo1D,14E2AK2Mo2Pp1:H2Ug2ap1u+2Sd1bL1Ov0Qt24d1/,1@s2[C1/o1741G+2Zh0[;0@m1,P2V]1a#1ta1cG1rt2IK1Wb24!1uJ1[M1hg1&$2M$00:2n32*d2[82U%1ql15p1EB0)U0mi2Et2:V1e)1A@2W61BD27u0;o29i1qM0/%1lp0:;1#m1h/0rK1hU2J52251PO0]O1r1@C21b2YP0dd1&&2%d1'G1jw1j}1}p0;*2fY1Yo2PY16h1qg1!C0x90dF0kW0kz0rm0hb0yQ0l{1Jr1f@2>n0/*2,s1mM24B1u<1i%1Qo1Xd0%b2Mi1I41D'0;81C*1s]1)k16o2fO23e1RZ1DG1$!0}U0#90@t0+X1XW0ve13[2wh1Tq1@z1VI1SM1&71*[1q]1ck1UK25g22626y1xK3Ea22e17H13]1D^2kc2U41uf2NT2ZT1a+0p31B*0}g0x318y0^=1jP24L0;M1ue2hi0^k2;f1pU0o@1eX2N)1d11270&t1aK1Wp22n18B1?H1Su1v[2lr2rX1441Js23p1%K0&?2{o1f]1Ac2a52H80}N1qm2VU2Q&1W40]#1V'1Z:13f1Ag3803EO0ix11N0[]1S}0uz1CD0^E1/[2MM18i2mD1N:2571@m0tr2,v1;S0wh0p616]20g1XX0v:2ZB2^h0eZ2WD1!M2J71%:0jo0re1$u1m11AX18g2DB22=15d0[G0#X1y]1EF0)a2j22JW2K!0'e2KH0i71A>1F^1p)0?r1N%1ZX1JL0}K1$B1!T3KL1=c18813%0ad0W22<&0#20l10@s1z11#r1@10?g2Qt1QM1Kh0;:1cl0#:2N?0nR1^q1j31,&2aF2oW2#:1gP1P/1AJ1H)1VM1+$2aQ2VG1I;1s^0uM0[q0+[2f80q%0@X1r)0&z1O>1$;1JN1*e1nY0y;0$!2=/2Uy21{1^:2kQ1<*2XM1X20uv2840>90}/0:b0?[1BG0WQ1>d13?18e1O;0m^1w/0,42S/1TJ1JU2D/1}$1BJ0td2U]20G2#22K11:;2642Hc2y$0tI0e10'z0m<1Cd0=*0@R1zQ15I19J0yH1u'0$o2K526<1]M1#Y1L>2ji2Fw1g80f!0to3L{0{z1bS2l[1LI2{g0t<0&w1[W2S]0?80oM0yT16z1Qy1ld1gb26[1c&1h)0#L0y72G?2sJ1fF1qJ0hc0v31PJ2}51(t2IT1pX20=1'Q1t;15t2U(0{90wE0y$14q1i*1Du1uO1*&1Y]2;51vw1Y)0{Z1't1]W1fE0;L1Bh19C1W(0f:2E{0$L0#<2cP1)$10&1^!0%A1>B0#p0*01P*1$X1nO2qw2(o39Q0]}1a=24(1Vb0>A2'91Zi0#b1t}1F+1:D2m81K{2Yw10'0ts0q>0wJ0y)0!^1;H1861BT0@+2lM0{52b#21P2r+0/V1?o2Sm0ZK1*@2Ro1TB0<00t{1Xu0xi1YK1?42b91v+2{&2s90uH1Ge1)s0,A1K10xt15a1{#20A1mh1ly1th1F&1UX0][15?2a01f[2k'2q[24x2Rl33o1bF1P61}:0)e21g2B<12B17K21M1JY1mY1Hd0&o1xV1Dg2912#81)81Hg0^:1QE0u#1A526b2FX0sX1Wy1Jt2}u1OE17X1w32#90y>0;(27k1eJ0{?1r31x90jt0@N1Rk0(k1Kl0Z80vv1EQ0(n11n1ih0YG1Yr0;P2C#12H2eE2M/22C11'1]f2/#1Ks0rE1K]1r{0>C0]b1yi1mT1};1&:1}'1K$2Uu1vG0*K0'32ox1rf2(80#l1W^11o1%G0l'2g!0(]1q?2Ag2Hv0/j1N)2Im22z1V72Nq2P$2uJ0ug1XD0ij0pG2;i1*d2DG1uW2k00jF2^>26]1[/0/R1>p1M}2O%20U1:N2JI1mj2hP2!51[C1E81i]0=12zb1AU0ti2=<2Lt0+d0,{2Ms20737n0w]1CB0),1y(1#,1uQ1X11Tm1/n1eo2Sr1YN1,i0}C1C11C^1$i0b(1Hr1dJ13m1L[1QO2/10dN0{K0+i0&61;%2N91@{26&2b22Ey0j$2My37;0cH1hM1/O1YX21e0si1<[1Q}1Ut1?%0lc1*i0S!0i}1?>1Aj0@*2r(1n>2Er0mV0eJ2w2II0eW2M#17/1Ss1ng2HQ0cT0yc0cE1gi1i?0f]0f41Hl2Zb20{1YO2yQ0nI1VY2kO1+V2ke2U[27U25h23(2xQ1>Z3Br1Yc16m0f{0q[0tW1Em10<0pO1nW3H=2tD0zl1EL0>i0A1[T1h}2161yC1il0eq0@Y1jQ0rx0bp0vm0;S24p1ty2zh0z!0^w1?d1iA2Id0jh1@}1sO0qU1Mu1#c2TC1a{1UG0/T0+{0tK0b/1s70Rd0$M0#q1O?2Od1rl2AC0l!0zx0xZ0gq2f(0z;0'E23i2qy1%!1[N0Z<1g41Iu0kL1:n0er19m1P'1&i0:829j2Zy1CA0n51FV0WW1FA1jR1:G1q*2Hq2iq1],24O2Dj0Yg0Tu0TF0Zz0Xx0!;0w*0RM0'x0jw0%e2OG2He3Up2[]0kH0+70@<2400dq1]a19,0m12an1RN12'0[&1Z(1,Q1d82;J1i'0gb0l,1*,0Y$1k^0?10ra0m$1$p1[01+K2C;23'0S?0sD0%$0;j0o{0n628e2P@0h=0v00u;1Sk2E[1:'0ke0we0Yh0yw00,0jp1{a1jz0gW0XR0e21al2/<0>y1>^1@@1op1H+1pN1E}0]w1z!0=t0g*20a2!%0u62a{0sJ0P&38k0z*21(1h&2A[0J&1ed1h@0iT37V0{m1Gm1R{1r'1?81Cv0Z:1ts2301V82Cu1'?0)91?N0gH0ch0PI1M'0dG0jV17[0ik1yH0oO2n>2Zf0j80uT1wK3291mc0kB0e;1E20ae0<&1@)2Ry1@O19e0^?1yB19R1@c2DP1i<0;B1vT2}!0ZG0Tx0K?0wC0RV12Q0m90it0cB0ci0*y0QH0ea0YC0oX0qc2ud2lb0^l25V0;D1/#1S*0rL1}a1(J1A21yk0{p1Ox0TM1Zz0&K1E<15z0pP1p[1l?1U/1I:1w%0kU22K2pt1Wu2Fx2IA13S0h40S>0dI15F2b:1EO0W92GF0cW0YD0vU0p70)%0b31O20lU0#u0[{1w[1EZ0qq1gw0lx0?L1K>1f82AZ1c'21@2QB0W51j<0fq0XP1VJ0kI0',0hA0c^11I0m40#61+g0py25F19X0v*1R)0a+0fA1Qs1!O1bi2]x0/!1t{2C%1UM1^D1m<0!p19I0*42*>1k}0,;0[40li2eO1;^1>a2nQ0vK0oD1tb1$M1sD16>22O2F;2fb2du1Co0Uo0100zm0iI0>J0RN0)}0)f1)N1zB1SQ0R&0x!0TN1I80YE0Ql1F41yM2U!2In0jZ2c@0h(1Z80}O1X,1He1*^2Mm0tT0hk1I#1,028d1s31?@1/X33E0,*1sS2r*0r62KK1pm1*>10A1U;0cm13W0*A0s92()0s41jZ1HA0Zv0(>1AY220x2dt152%@1B92cQ1i,1*Y1L+26M1Cz0{12WP1jv0(J0[=1QZ0n424E1FB2Q12q21(l0&'2),11f10n0s?0jC0sd29w2lw1Fp0^>12/1zT1I!2oG2np0$B2WS2L+0iy0Oo0o*0UR0({0Xm2N^1T?2Is1d{0z82fq18v2CM0]n1dg1@X2IU0ym0R1%q0Q(0Th2A<15[1lo2590/70+10#W2:;1K;2L{2wC1L!0mX0n$2+%0q]2hg0QM0V}0pL0xp13r1K310d10}0](1T@0k$0cX1fP0tA0%U27{1E$1d)1>>1yU0#32>B1,f2u701L]2Fi1;l1Rp14v0bZ0f50:c1GH1Z)1Ex0,81at0;]1>V1A61Gc0vi0Sb2If1570yd0*@1*R1IC1YL1D70/o2K*2me0nU0^}2hN0zJ2d!15x1=L0sq0>n1:h0S;1c40oR1Nj0jq1rM1aR1BE1ON11w1ad3Kt14]0@^0]<20R2Pm1jB1L93KF2m'1VN1QU1xY2V)2m)2Mk0JT1Bs1GA1I90zI0j[1[J1D30tJ0,e0wb0zh2oH10b&1C$2gL0>41Nc0ud1Q(0s50*B1VR2!S23y2Wb2ul2kz1Gn1]:2qp0#=1k;1I+0rG0eK0X,0R?0af0ek0eO1sv0?k1av1$g0!w1A{19U1N<2P;20z1#82+U1+c14:2Iz0<}1]q0IE1H}0L511P32w0ls1582M(2pq1[f01!W0rH1ZU1nw0sN1X#1M00qm2U^0x<1M@1?^1&1xP2&N31&2bu1JZ16T2eW1pS1&*1{d1!90*i2IM23h0U+19i2f<1bU0r&0&21Q:0rg1AR0x*0MS2U22tn1Hy0=E2xe2^(1oh1n/0gx0}^1c{0se0>t1)42Uf1A=1MG1Og1901f41,=0,f2g[1a}2#/1D>2QQ0hd0=^00xu0yJ1*j2J11x:1%m1OZ0f$1>S2IV0<628722m0;#0{h1a814=1t(1n:1Cs1Zb1DH0#427Y1g>12I0qK1D=0'=1*U1S(1T{1rN1Fd22k1U02GN0or1ea1150nm2Rp1#b1$22M[1*'0^M0^O2q^23E2y'1Ji1xJ1^]2?*1fk16u2Jj30%1F81*t0121Z[0d]2kE3RH1Ps1Kp2UM2171L#1a!0{&14b2o!2YA0p%0?u1OF2?N1}71J,1'*17,11913n1Jn1EN2@50+u2GH1,{2FR3S>1r[1W!0f&2LU1UB1lN16O12Y1$c23A1Oc1>j0pk1fJ2Nr0Pt27!1wk28v1(Q2CF2jE22h1{u0p80*U0r10[k0il0lZ1y{1z'0eP25m0U^20q1BH0Q/0=]1#X1eF0up0l81W70ib1Ay0[@0]Q19&1gW29V1[e0*o1d71[G1)U1V$1v)2BK1^[1Hw1YC1[(1mH1Jp2Q91Yp2222{/2UQ1OG1i&1j+0sv0ui0t41UH0{k0%M1qk1pk0?Y0@80{a2v/0)j0WO18m1jm0]W1k*1M[1+]1{K1<,1qP2Y?0?}1NF11}1{;1i42Mj0<91Cp11d0<+0,#1H10jd1gk2El2qD2R;2Tn1HJ1%p1FW1X'2q91LA1bx22G0Ty2LE1'50Zi0^$1FZ26r0%{2NQ1,'1de0y<1ev1Zm1Lk1QR1#G1z617f27T1tc0;C1PM0nO1hj1nQ1go2ft1Gd1Tc2%]0rd0Z?0S&0i?0v(0Y<0k50dY0ag1Vl1!E2J]1wa1Z;1#g1y%0kf1)h2^/0*u1mn0$H0j93842a22kR2S}2J<2DI1ys1(E2eT1dL2++1S/1cJ1Ap1Y+0%%2J:1!g1X80131no0[(0a[2h?2we0he1jq0=i0&30/[0nn17O0:m0@=16B0@H0+*25O0ng2Rw1=r2N]1x62^d1NJ0r92I00*E0i+0wQ0!k2Jq1#?2G+1C90[I0!M11i1}]1:31wE0WS17(1;F0zt0Zc1R52*b0hJ1sH0ZB2C<0)Q38p1vq0;10&k0+40}11%Q0tB01S+2L:2(41&m1Az14m1242oo1P+0*W0{F1LS0/h0vf0,/0la1!B1/42KO0sF0s72fm0p:0wj15926z24z1kc17Y1:513)0:X0sw0nS1sb1(h1K40cd1ER1*21?{0l+0Rj2le20}0Y%0}*1&W0X?2H,1)60o>0sx1(m1c+0,M2$I2!q1Y52K]1:{1]20d/1'#1rW0wr1Do1KE1li1p822j0m*0qg1DP0%D0SC0o41z)0rQ1s00gR2mE1mW34A2Vh1;O1Ba2+o2P:2@A0zi1HK1Wh1gD1K92n/0]&2$A0p}0pb1mQ2z#1GW2qV1qB15A2sd11=2VL1JJ0im1p00;{1zN0qs0{[1q!0$e2Ri13o01@]2EE2%m0%}0=S0Wg2K&0$z1R?22LV0pp0($1Xn2Ch1j=0$d0zj0}80l?0<:1*f0wz0m[0e!0&Z0[M14V0/H0tX2CI12&0(M1N*19!1^20ru0r<0c90w#1/b1K=0ph20*1iE0R'0dL14}0v62Bm0#$0#g0o[1{]1To1LL1D[1H61L02[E1LV2Hj1G{2Wh2#m0;f2l@3M/0x72Aj0)E0,p1;42oA0Kj1hK0$s0!20tG1](1ni1*l1&+1KR2*=0qS0gY0@U0*P0{>0&i1E!15&0jr2S$1+I1I(1Vg0[F0xP0?61SF2eK1H@1Ft21$2C:1J920u16b0(I0t,0@>1+z11L1BU0w$10N0Xy0$21xl0c'1J81oz14h0+Q1]i0rM0)u0h[0b413T0+C1N!1B#10q0tg0fN0yi0{Q1310&r0n%0=Z0/e0q;0'y1aU1vF20X1Uf0kJ0#Q0YA0d@1o,2m31Xc2Q22n#2S%1o@0+M0ty1v]1NK0'!12u2yb1dk1Wj0rz0,i2nO1rZ0z(0qJ2gd1Oq0g,0wR1N]2Ji0:h0ig2cW2i%2Az2CQ2Qh1Gv0[)0$30&Y0sA0/Y0+H0;I2Ph0!314K0]B1Ct1Dd0Re0+80U20}e1gh1^#1hO0$?1y@1F;2EL20<1HV11E0gc1]60*50ap0N41W;1Rq1E,0y40T811Z1+/2TP0]k0(,0Yx0?x0>f0ub1T>1D}0/W0gs0}b0m:0aq0!H1s11or0k!0$/12K0Wk1uZ0)W0+D2GU1#w0tL0Rz0?F0St2o^0w%0Zr2Zk1;{29u0$f1)92Zc1vU2I=1&^2h)2J#1jS1$j0yA1f/1Q725J1p@1T40,T1'h2xw14#2kr0!u2vc1]D2H?0+l0sk0)v10]0+v12c1Fq1?v0$<1Tt1)&15b3Jk1D10&N3Av0v81/g1QF1=j0^R1E01,x1A<29/0VA1QS2R$1Ei0!I1Ln2JQ1iB0<0:&1,U2jp1=40!f0UQ1B11^u1970?V2DA0TK2gV1Go0H{0,:0nC0UG19*13M0/f1Pb1GX10x1'D0I[0es0sr0Rh1GM0}Q1x81500e}0S*1@Z10I0141De0j^0o;17g0Z/29225/0NY0=T0Xc36G2Ln1+}0eb1O=0*G0{60h81Zy0zz1(V0IB0ov24U0/Z0{u0g&2a^0w?0Z217%0/P0+s1oK0^41ak0)!1f<1)q0(L1wS0fO1t!2Hh2J;2F&2f224Z2jY1x?2oZ1GI1Pw0/:16f0+60#v0S10jg1hv2@;1lz1Jd2G60hi1^n1/K0ce1[u2DU2g61X+0n{0[D1%/0MG11:0sY0mb1&v0W!0Nu1{,1A)26H2Nu3S=1Gi0N#0r*0;d0^{0#h10)2G(0i81Lb1rQ1e$28X2FM1er0Y>0Yy0,%12bI1930Np0?#1e>0O!0oE1B517o2f90h@0]l0Tz1xe1NY1t92P233$0@;1Yz0%l0*;1R81Sp1$H1x(1+%0sO0(?1lK0ul0N$0aV0gZ0pr1is2e;2Fo2bz1%?0oJ0Z(2M51Ng0^&0w^0R*0V&0fB1JH0+w0,q0^J0L60]m0Tw0Wz0bd10?0rh1B=1Vs0#i0}h1kz0'f0:*2:<0ww0t&0dx1s50J,2St25e1'g1hZ0(80z}2wZ0)n0+;0q90oA1sC0Wt1Ha2oi0bt1V/2@10?y0wM1Q80gX0XD0o00nG0>@0Xz0kO0+y1c%14$1AS1c#1Y61DB0?$0vy0X+0Mj0hR30312g0=[0P{1i[1jW18O19>1EK3380ip0XW0={1mp0,P0qx0qL1Za1i)1q)0v41i{1GY1Zu1oa0b!0LQ1o!0T/0)G1E>0mc0=d0W?0P>0Gc2J20*v2uv0Q10[r0wN1U70vM0g$1Km1Kt0n>0*60f80p>0,b2A70iM0G2C'23t1CX1s@1<]1(U24{1&[1em20O0]Z0,c0oi0h]0;U0UL13;2#A0lV1]N0mF1=M1i;2G819A1La0%'1>21R[0%50vJ0&x0@L0lv0UV0uW1<<0de0PU0^51gL0']0th0UM0#T0iX0R/0WE00't18F1Ms1V?0*s2lL0UH2Al0X(0^/0o10Pb0w&1h*0KX0d60ji1Fk0P40>*1DD0WF0Q=2sA2s<0%N0c10uG0#@1Sr1[$0Zw0lJ0,L0xn0cc0:]1cU1J21oM0um1pb1h81N[1Ny1J41&j0]01LW1m00X@0k=0=w0V=0/51BX13B0Hl25c1VT0fr0XB1?T0h;2k81b411l0s>1qx1w<11j1HU0ma0!T0J%0Gy1PY0w70u'0$'2fA26>1kl18p1Ey12[1NL2yD0^912V0lz1V[1@E1Lp0b[0k%2Hb1K01Vm0+R0oz0e)0n}0t50VW1^j0W60^A0hf0/r13U0/?0#,0tP0Y&0gg0]r0aF1IU0#k0X}2:'1nL0yW0n^0=N0=(0)A1L$1('1ro0#w1Lx0=p1'f0@q0I,2bw2OS2'#1$31r%2fe2T$1Am0uw0n21kx2vm0tb1sd1;21E]0zQ0^+1R*24G1ig0^C1O60)g2JX1im2+v0j20,U1Na13E0n)33P1'w0pD0Nf1?Z0'r14<1>H17j0n(1lr2ir0RB0;a2CB0^m1Pj0yo19F0<=0j&22<0W@1+X0t(1uF1ZW1zo2Xc28m0*20s;2ya1iQ0#!0!?0w>0$J0>(1c90l00<20?]01A/1SS0U]1N^32Y0i<0j31za0bG0b}0{C0/c1%[0aL14S0Y71630:#2[40]d1Up0tQ1180/;1BQ0!81F$1>t0ut19f1W?1y=0)B0>)1=m1!F1)T0wk1)J1vs0;b0zX0iJ0W#0qv1IF0t737h0rU0bN0X$0#?0h:1:U0[h2^$16w2IQ0YU0eQ1l02}i1Nv1X)1i!0>T0&u0O50WA1td0<*1)H2c81ia1{B0Xq0RC12}1a:17h0ol0Zt0V[13,0nD0!E0ht0]e2$/0p(0=P2350=u1IG1@#0>b0hK0{P1Nf0jx0%x0k00Ui3C>2{>1oG1}I1+e29A2FF1PB0]R0w)0)'2e531v1U}2X70E}10:S0l20MY0m(1l#0^Y28k0y+0Wx0s&2(N0eM2to0'20>P0'>0*L1Jl1c(0:(0=}0)k0X51dl0=e0mC0t00*q0op1#N1wW2?j2=H1tp0vq2%c14Y0>51@g0b$02L813v0:21V90&=0!$0v11RA2lc1P=1^'1g{1yP0e61NE1C=0:z1f#2eR0sy0r#0=k0vc0gS0:00{f0B%0Tm0GH0YN18w1S80<;0*$1CY10/1AC1pJ1M#0^h0;X1nI0]h0jM1dS19n0%41cA0i90at0c=0Wb0P,1RV0@b0y@0!r0X12A,2N{0fP0yR1y30*d0(x1V^0Pc0*a0S(0L?1Bb1}B0?,1E/1S=2rB2O+1!V0/&11p0&P0r+1WB0yg0!:0[J0WD0ec0PS0dA1QV1TZ2Wt1+q0cN2im0Vr0Wl0%W1WJ1,r0w/1v!0h^0,V2H00s*0c60hE16$0le0qa12C0r(2O#0aG1ne0tk0xB2Qk2#I21q0r=0@u0pd0xc2h<0$K0Wp0LS1Q$0u*15@0l<0[01J?0w[0nE1$/0D)1ae0Qv0rn1<30f}0so0(51s<1Oj1H<1a&1Qm0Kv0$60l)0>Q0Zx1dD2A10g{1s>0b:2SY0df2tY17y1pf0;921:1L;0w31BP0kv0Uv0a;28@1Ej0dg1^x0?30dR0fs1Bi1Ip0eh0/@0>w15f1DE31s0Z)0xJ0z:0sI2EJ1Mx1wX0'H0!N17J19O2JA1(S1O!0'D02QV0yj0CF0Wc0bq0mL0C=0DO0T)0L;0P81p>0QZ0O'0U&0hx0g;1pD0*:2a70}y0el0/S0os0pX0k318P1ft0%<1>g0X^0v,1S42KY1uK1w^1G61fW2I51dT23k1l,1W$2oP1Kv1)R2V;0U*0&U0,B0+E011!0P%0Vg0g]1fN1420%71Lu0ny0#N0+00$>0Vy0]D2Mx1>N1)%1H:0;42W<19:12L11DK0=q1;I1=D0WT0g82G$0Oz0PJ0OV0%/1X32ex1J<0m]2DC1mP0)?2yC0NZ0Xn2pf2I)0nz0sz0c;0Qx0M;0oj0%+0O{0U?0NW2R<0qw0sK0#%1eY0m,0n/0bO1fc1!G1on1Nt0+A1);0ie0{]01615N0v@0Vu0Pv0Wy0r70jN1Q@0O*0gA1e+14Z0f%2XS12R1M,0{{1B!0)#1tz1wm1QI1ib0)y0D0iq1LQ0cz0z22d,1R/0Em1n32+<1O(1LX1I00wf1gN2{60b70+F17M1p90/i0e/0h#0!j0^72L71?w2Ml1K'11<1K}1Ah13h0]:0ux0u,0>G26B1GK0y]0>#0:I15u2UC1Fj3EY1KS0'O2U01kh1ao1F!10J0&H1Ff0@n1A70+j10Q0ju0aO1qr0Pj1710T&0xz15'0mQ2pF0NA0Z>1F[0Qa0@O1$R0kZ1A80ue0#c1ix0hg1Yh0d^0y^1}K1+=0lO1Mc1S;0(@0sG10k0[/0,}0F@2@p1m+0mD14P0di0Q>0>60!&2Ds0jk0MT2h:0Hq1gO0Xt1zP1Sv0#A17F1WK1{b1ti1ZJ0#I0/g2B60d123l0zR0=B1Ii0V?0}F1wc2k^17]2sF0z30[90>h0+S2L}1f*0Ru2CR1]O3Cw0!z1P@0$X1%D20(1Jq1nd1VB1b[1M:1DM1nE1Q<1a?1tG0h<0pi19g0#50jl19z0WZ0=f0XG0f*0[T0OH0K^0pe0#{1Zq0VM1rp0u40]11Iq0z]0R:0>o0O>0a30b'0QV0y#17S0;0Ue0u!0?J0]s0w;1]L0:f17A2Em1g^0xR0hU2x<0QA0uX0#d0@)19+1mI1EH1@(0^r0(l1tS0tc0q021Z0v0F,0X)0k?0;'1;60:U0qM0[l18#1(y1C60ZY0^Z1%c0go0iC0Nw0j/0YB1Kd0fK0kd10$+0]!0I<0fc0cJ2Gx0mM0jS0uY1VK12W0rf0pT1K+0qd2MH17B0tu0(;26*0lh0,^1DQ0UN1cB0f317}1MB0/+1rh0Kq0Dm0Hm1hf0T?1^70'+0*80mW0ei0&y0du0i)21o1bg0V41&80ne0]z0Z^1'M0PV0y00fL0Uj0i#0zr1Ux0;O0%R0@V1X40Fs1Y{14[1AH2q0JQ12M0q&0=41cm1KO1lD0!A0L%0ga1Z]2O?0=$0^S16X0q<0o:22I0kR0Pw0Pq0Qw0Kr0Jb0PE28M2H11la10t1#40o!1+x0ZH0;?1Tu1iG1y$2nA1:s21]1tm1{X0f,0Gf0#K0Yi0P01[10rW0jy1Z@1Nn0aC0ed0oc10f0Lc0Wn0KR0,k0Qo0;k0oY0p#0y10ca0v#2Me1M1HL2PX0hu2(C1zW0AJ0MQ0MN0)h0c$0R(0MC0L,0MH1Qh1SB18}13d0qr0yx0(^0hj0Gk0YW0fY0=x0#R0+p0x?0kP0]t0LZ0MD15E0bD0o60G$0$g0]E1a)1x*0UW0X:0iP1wO1k?2N[0Fe0%o28L1aG1et0lK0bE0m{16J1B$0a&0LT0J>1Ci0UD0R50FV0c41J$0rO0yS0Ln1p40r?0Aj0IN0SP1wM0If0vN0/U0m)0K;1cV0yr1%21TR0K90+G0nu0oT0ws0g(0VV0@d0q71A(1,C24y1,!1ud2Px20J1s,1=G22X0xK0Mg0tj0EI1Xr2ra1uE0?21KI0zK0gF0jv0VF0aE0gt19w0*k0r)0P$0F#0VQ0Hw0Jm0Px0La0iR0H!0ih0vO0Z@0dP0rb0qP0cC1TC0?X0HF04u05#0Vj0Jf09}0AE0}o1Bm1ZQ0W/0dj0}R0)61UT1&r1#j0hZ0F]0N:1/a0P10iv0u00ba0N@0Bu0Oa1xr0qN1N626w1UQ27^0U@1720xo1771OR0/80!}1WI0=513P0$m2^&0xW0]U1Zh1V}1y,0[y1Z{0$Y1^a1r=0)o1�Va1Je0sZ1J@0@00p^0a(0Ux0j*0{41k60gp14n1O'0S{0WR1q'0x/0dO0rI0g/0%V2Vq0xl0jO1sT0K01g/0Ro0Y?0/a0S@0R11Sc0d=0)H0><0Q*16,1Q{0YO0bF1An0US1ip0B:0B&2GE0E20I60[X0JX0C&0x50zu0@30VC0cn0MO0IG0b,0FK0ML0TC0JL0I20PC0d,0dk0HK1kd0RQ1Ur0^H1<=0yD1^}0E)0qC1S50)X0+%0p=0/30+'0E:0dT0Vp09n1Sh1rz1zs0':0Q{1#P1{S0yt0&V1081SZ0jG0g)28g1*v0lp0}p2de0Ou1PZ07}0=y0*f0uj0xE0SW0a{0Mx2a=0Py0Y112*19[1x40>;0iz0kp0{i1oU09(07#0G]0,$0I#1%%0Y20{j0qn0QR0B{0VG0eF0y{1ND0Hj1:P0:P0Hd1HO0M/0T90a/0Yz1qj0uZ0I>0M(0c!0)b18:0R82a@0ZC0An0Zj0T;0ZJ0cD0tq0y!0ge1Wm1+J0k{12z0t[0bJ1Xq0}m0;v0m@0wl0<)0jK0oq0w:0<@0PZ1Gl0}S0;Q2L40Ib0R)0j]0Jp0r{1q+0n00m30s[0Y'0WU0fI0l[0?D1@S0g50kD1Lr0hr1q60Up0u70oN0QF0,I30U2Ot2PM1V#1?r0/p0tY1o81Wo0]G0[a2u12B>2K00c&0O21P70D02=80Xh0jH0r}1:^1H70e:1fy0G?0AY0Ol0B!0IX0k[0T21^r0m70e@18z14g0P+0>V0a,0@90e'11C1Gy1821(>0z,0yM1,Z0et0!l0/,1SC0kQ0kq0&B0:@0cR0r]0?:1R(1(b10C16x2N!2IH1u60vr0i,0jT0iY1k02c%0c20Xd0$51d?0K10,)1GJ1m&0zS0O90FD0S30g02n+0!X0$,0IY0M}0Q80Ve0Pu0r@0v$1aF1iS0*z2L50[+0@o0a70Oy0uu0h'0QI0NL0g90)q0bQ15g1u[1A&0[>1^K0ze0>B0tw16}0=V0HZ0ka0B;0K'1)+0Eb0bT0bH0Q70$90<<0aP1sn0MV0M#1L:1k80oH0]V1G'0TV0?P1Om0h/0mR0wW0@&11[0:B0s@1E*0eG0VT0YI0p90ZR0(b1ZD21z0jD0@e2:e1:T0oe0$0LC0W:1CQ1q70QN0a60?A0(E0[d0P20mA0YX0Ff0Nh0Qy0P}0a@0g[0U/0,C0H@0k61Kw0CV0EM0ou0l*0U30S40SO0ET0Ks0VR0T,0Io0tM0AL0Q?1f311s0Vk0#U1N31!=0d80YJ0i/0h)1l@0X'1q21I&0u<0N'1c21FH0E&2Ar0k;0Gb0ZS0Ci0N51B/0H}0b>0D?2NA0n?0T32Vy0W^1Mi0e+0r'0'41QJ1%r0r$19P0^01G:0R90;E0@y1u$1Fe0bx0d&2J[2Qb00K20zA0;@0Hx1r00ZZ0l@2s81AN0C!0v{0Q40{V0T*0u/0Fg1F<0tR1rO0Ec0fl1cK1JQ0Z,0#80zL1M{0}q0{E0mm1pK1(01Jg0iV1'n1Cu16;15/0Pf1eu24e0k^1nV0)70+Y1VC28b26s0{w0z?2/J0P=0XU0Ur12t0V{0Z{1E+0(h0Tq1220be1JM0uU1PN0:)1PS2c(1FE2W]0Q219G0Qp0e#0O@0/00^u0N[0Op0p)14i0lG1?20qi0nF0k/17V0yY0;/1pH18j1B60L(0kV0n@0kG0Z$0aH1ay0n&0pY0n+0BZ0Ic0@j14!1,0PH0ct0IK0gn0Z#0>x0o#0'(0YL1lj0Wm0Gr0o,0Zd1VO1[s0U}1>=1>U1@t1b90!B0VU0*{10g0*'0]]1p,0kS0qy16+1%$0Rs0o72SJ0(i0=c0pE0f?0/90T10bA0cL1CG1Uv0kM0t;0'00lP0Kh0Zk0U90HP0OW0aI0dv0C>0FP0Ey09;0Gs0Dy0J70hD0e=0l/0xU0uc2mr1^g0yp0+(0v+1^d1RH1Qc0ZT0,50IH1MD0Z+11x2Qn0#e1q02Dc1KY1i20rP12m0mI0sp1U90en0bf1E%0$I0Cr0?90xb09614u0HX0LM0!K0H#0fZ2?q0IZ0Ee0C$0M70VN0jm2TR0t10rc0p50?a0fa0a*0?B0OQ0HC0K#0F11l(0Ld0:'0vW0mS1+N0?z0>?1FT0C221*0J^0Pg0CY0Zs0?'0?70>g0d*0i>1Ce0[U0wS0XF0b]0e,0dU0U,2fg0KS1YP0WB1pg0X70fp0F{0{N1T;1Xf0@l0$11v30yZ0p&1zD1M10O:1ZR0e[0:,0an1aw1OI0un0uk0vt0cw0f)14'2411X}2LF0*H0}c1Zw0g:1451051Us0H:0bg0?b0B*0yu0[w0!x0*(0s]0h$1dh1+<0qW0ej0rV0ss0nJ2LC0xw0TP0R215k0V00TQ0S)2310R,16?2q#0j40Bh1Ki0VJ1X(0%F0q+0:j0}20p10:Y1(20He0rB1I)0WJ0xq0)M0SS06T0h01q/0k40NM0H'0PT0W+0aR0SI0Gd0Pl2hK15s0QS0FX2U+0+B1Xy0v^0'n0?t0dl0ys0/s0o=1>T1H#0FL0By08c0Ge0Ls0@B0Uy0^n0lq0#B0<[0nK0?N0l#1JW1Bt0VS2be0g}2Y@1/z1Rr0@'0Hk0ai0o81mF0Cj0T:0ZU1Q%0Ly0E%0kb0e00Q^0TD0YY0Ii0,&0170b@0S20dt1ir0?H0l%0!O1o90:J0hL0%Z0o'2I/1Bc0ms0%!0nb10p16t0lB1fX0#Y0$C0f[0)C1,a0=G1=T0Lh0@@1gF0aY1RO0q?1Jj0x+17k1>l0sB0*X0=a0F&0Y,0;m2VA0,t1kj0[s0zY0OX0a81n<1wA0l]0dm2/&13L2r10X=1#@0/O0UJ0fh0pm0=<0ZD2y{1yz0x:1]j1Zd1Ra0+N0FG0Z90Y)0xm0V11b00iQ0q^0ry0u%0?>0WK0[50y80p20t=0t]0u82=90=)0OC0OR0j=0W%0fS0Xi0eH1FP1F92o:1u01fV0dQ1P,0(21Uq0P]0j51(e0TE0J=0Wr0sS1dp0@W0Om0eR12;0GV1/c1Eb0W)0jP1U*0Z'09y0hM0&s0Yn0S51b<1GP0'I0Lv0DT0C10Bf20P0Un0P;0D&0TA0BA0S60L80$u0xx0H/0Qq0N{0'Q1ei0T(0rJ0=A0gN0Lw0YQ0gi1co1]$0q520#0ha1p20Mh0Ep0OY0+)07,12h0L>0M00?=0wi0++0Ua08N0TH0Zl2WO0SL2/A1%O0/m0U>0xd1fS1Ij0/I0Yo0Rv1VL0g11E(0c/0i10E*0q30JY0X[1ec0HG0j?09<0nH1S)0Fp0rr0i]1!r0Pm0ua0Ma0da0tC0?40Bi19?1TH1zi1G50q=0cf0c<0aK0!@0180Cn0Vl0;J0EY12N0J}1G#0dn0A:0)N0Rw0UE0s10c)0uy1Al0V;1RI0Ti0K30mj0qQ0ki18>0+I0Ka1!L0EJ0O61PF0M&0=l1=90wx1,$0cK1P;0Za0Ix0G>0Cs1DR07(0*/0iF0{W0j<0$h0w@0sa0d}0Ps0Xj0nw0aZ09G2qr0p*0F$09)0S+07f0h{1be0Mq0,[0580hn1]X0D!0bu0j60vR1Hm0e<0d(0tD0cM0Kz0190v>0%T09f08x0U00LI0Eu0c>0zB0j+1Nh0cG0nv0mN0vs0'U1DW0n30Pn0ya0QO0XV0V:0Xr0FY0aU1020*Q0w90LD0iK1{V0@Z0]?0mv1uj0;00LN0Ca0dJ2]c0FH0u>0d!0GS0VO0a=00o}1[I2D]1H,1m!0hN1J60H90L)07P1G$0Jk0<>0Mr1HC0E40vS1aY13F1rH0b%0aW0Sn0Mb0Jy08V0C+0HR0x^10h0hO0Uu0T'0V(0S=0RL11m0sg0oZ0tZ0l>1ED0/]1610av0lj0'50MZ0=%0ez0v50wt1n90F62WN1EX1Wg0J10bh0RH0bU0Ll0je0JR0K%08k0^T19Q33>0NT0G;0So0xS0KF0Al0Sx0(S1ob0qe0XK14r0jn0p01b;0rN0=I0I;0h10(90sC1$90o/0^30n70)^0>Z0HS0Sy0r21&d0Sc0V20V,0Ob0Kt0;g0Wu1w@0yk0Uk01A0o90s#1Z30%=0G{0eI0GT0HA0;20!S0)I0GF0W[0l40Ns1VP1Rx0T[0G%0ni0p[0'j0pV0%v1XR1Y#0?j0pK0yq1&51/Q10u0fC1VS0G80CG0<'0B)0FO08l0E'0Q$0LW1TF0[}0(j0J;0'l0P!0X/0gB0hy0Yq0Yj0Xo0Q+2AA1:t1CC0U41W90c[0(T2df1K*0J)0Ef0*R0mn0;s1TO0Lk0Pz29f0Mu0(y0wu0Yk0r[0c50dw0m#0wg0pM0@J18n0D,0,O0Wo00qz0R{0CL0Eq0Vw0VH0Bm0KB07N0%@0+=1Nq2=C0iO0Xv0G(10X0Qk0Qg0j)0zs0fR0>c1550Fl0'k0!{0ks0I91Zx0^80W'0fQ0)30i&0uN0dC0wA05o0hX0ar0p,0tl0[O0?v0f+0u12)'0Xw0Sk1#;0$%1iu2dF0dV0VK0s{0E,0Tr0X!2Z:1zd0A}0TG0Z40i^0HU0/t0DM0Zp0!P0)r0Rk1XE0F*0oU0&W0q10B80h}0Z%0'c0,d0eD1Id0Oc06h15]11]0gI1y^1F00)813Z0{@0kg2AG1Vn0}%0=C0eo07Q0y'0%20SY1V&0A20n<0H608y0kj17p0+J0!416G0UY0X80VX0^t0]*0d209Z0J<1+Y0/60Gg1/;0Tv0${0v}1Y$1$A0@/1#A0QC0Lf1Fl0g!0zf0P513a0b80fD1:w0EK0pN0nQ0vT0mT0H21w50no0ZA0uo0u20RI1Wc1ZK0Ms0bW0&80uh0Nl0B,0PD0EV0ZW0ft0h&1/10D80V)0C)0sT0Uq0KU1s=0K$0JJ0H50&]0mg0hH0DE1Sl0C'1&e0{b0d'1gX0Q!0MM10L0+#0mJ0$T0vH0V<1Pe1Tw0(&1W51GN0ep0H[0YK0Vb17q1/Z0?!1cp0dr1G/0&,1hd05f0U#0/y1rb1ct0u90j(0=Q0&%1A!0uV0Kb0do0GK0L40ic0B}1!H0#}1pY0C30F70990x@0$y01B1}L1GR1MW0Rx0Qj0e30&v0db0$j1u70c+0k#0fj0RR1R90id0l:0>]0pq0T@0wL1bG1=n1@T0H$0J80R[1JX08E1Pl0a#0/(15h1{D0U70zk0(#0SF1xR0Vc0NU06x1EU0U(0bb0S70+20lH0;$0sE1[80s:1bh0om0XQ0(:0^i0F80E^0N>0Q)0Nm14k0OS0<]0@!0{'0e*0)F0]/0Nk0&&0Wv0Yu0OG0Jc0Gn03m0C009J0L90G90Ju0G30Ig0X>0Vm1AT0ds11e0a}1C20+a0R<0*S06r0C]0780Me1a20Vs1BF0VL0NI0)O0h+29F0{q0T#0vd08v0RE0e70LE1Q+0C40J!0L+0890E50ZL0}+0L}0Ag07G0Cg09^0I$0Ai0%B0Dn0Ww0Z70sP1Or0%X0^U0ps0Jz0Zb0yy0TT0O]0V60sf27V0gl1Md0%Q0q{0&J0k70+30)s0WH0hh0c00#Z0Y90n:18{0w51QK1Nz1I^1?D0G:0ox1GL1Dh0D%0ex0To0Vh0IC0]50I%0Of0#j0lE0d40G/0Lg0J60]N0f90Il1/$0Ew1[+08S0ff0X<0z@0d#0Fq0Hr0i00Z00GU0T$1SG0?{0Mt0,J0}I0ee0ZM0$^0co0?%0st0m>0A)0jI0>:0#S1IV0UO0lw0q'0%&1F/0G}1m{0Ht0Hn14H0HH0L[0,10??1c^0Di0LV0l$0W]0;i0P315!0Sl0Og0Ie0S$0JB0M40OJ0Xp0CW1F)0Br0EW0AZ09a0M=0IR00a)0WX0bv0,Z0TW0$&0ZN0)l0Uf0%G05u0h%0Ml0S[0Q&0Ke1670v;0KV07)0Hg0M80iZ1Ow0S80sV0pt0Sp0F21>:04l0TZ1pi0WC0]q0gu18S0!U12i0y61[>0i30rS1^30a10!50Uw0O(0hB0nq0s$0t:0<319@0al0r30UA0B^0oV0M90E$19S0JC0j>02v0L105D08W1&}0dy06M0802Go0my09A0SH0EU0F00eN0Ts0Ws0F90Nq1910gU0>K0#V0JU17?0hY0t'0x60k10?p0RJ0W,0TU0U{0nZ0/=0P:0Tt0Qn0#[0q#0K,0PF0fU0IM0r:0{#0gO0rs0+!0W00,K0WL1p:0U51H%0Ek0Pk0Cu0H<1Fm1h90Gl0i*0BR0[L0Sg0*%0o]0fu0if0lk0PB0F}1]I0y:0Iv0?m0XX0f@0}<0Kf0Bg1Oz0NR0G'0x40Iu0GY0QJ0;50zM0f'0nr0Lx0S}0O,0u)0of16g0oa0CT0A@0a]0Mv0<40*N04D0A#1Di0Gh0G)0cY08L0gG0>00IU0[K0c?0G<0Oh0rw0Cb0C50is0PK05407<0A'0ef0IS0Da2890Jr0+K2xC21B1=^1}w1Bn1RR3I{04=0Jg0kX0Ds0u30ED0UP0gJ13310>0RZ0I:0T^07T0Gt08m06y06,09z0OT06009u06s05K07[08508F07H06@0j@0A&0OL0E@0B108204V03h0Bc01C03606t04I04306d04T06u04z0Cv06n0W&07$03i0R%08106z05*07u08X0B[04R06i0Mm09?06*0410FT06W05S06N05T09K0D10CX04K0790EN0ZF04f0Cf0DW08u0A807%0fv0C;0VD0Q;01D0%w0Q'0mo0GL08J0SZ09[0o<0Q[0!y1B]0!90pC1160[o0=D04v0BD0E#0bo0Gu0SQ0YM2I]0Go0E60xe0Tk0rX0sQ0O70K&0AU0D90E70G^0F!0Ej0920s!0ns02]2N711A0Tb0P90bI0QB0Oi0n'0@z0nN0e(0ST0jY0+f0500E30X]0HD08q04}25x0GW0eu0VE1$k0Ij0A<1Cq0W=0X%0H%0Nb0z50Rl0HI0hC0F<0m!0IA0590HL0BU0XL0O)0GO0Tn1gE0V90Lm0H70PX0H>0Sm08h0B712T1%]0jX1=y0LA0Ez0Cc0O00470Fu0N&0CJ0GM09@0CK0A30K[0J91#i1Av1np1Ix1SH0K509R0!%00FW04;0C?05a0e90J/02t0Ba0aB0C[0I&08@0m}08T0Iz0AS0L@09L07g06j1K!0Vx0vw0SJ0JV16j2dQ1ym0}L0iS06:0Jh0h!0^a0w+0gw0X90{80I?0g%03G09t1*n09:0M:1XT06P0CD0Z}0Vq0E(0W*0jJ0Z50L*0W<0xF0Gi0IW0mY0DU0U=0P60U$0NF1TS0yU17i0Qc0B<0Cz0*?0k+0AN05704a0B90o$09T0br0Qd0P@0Js06Z0D#0Ji0Iq0WM0/40D306%0rD1cn0*}1b@0gP0Kg0PN0d{1SV0UB09M12U0gy0jE03z0MW0J?0T40390Hv0=b09O0j70HJ0NJ04G12r0OO0,!09H0740CP0Ak0Q@08=0L#0Ph0Je0Vo0950tv1c*0kw0Z=0bi0Gm08P0)t04L0LJ0KJ0G40yz0Es03(03,0w20LR0Ax0EA05y0i50P(0F'0nB0oB0BH05V0KG0E/0ZO0Zu0QU0E[0O}0Hi0Tc0J[0R30DR0Bt0bX1OW0BI0D40bK0Co0Ia0UZ0D^0i:0SN0H31a504x08M0Bk09$07*08d0Dt0WV0X;0DB0&+06)0JE0I10c708B0h?05g0*O0&E07Z0C{0Lp0<50:G0(*0T+0Fw0Mn0I*0480Sd0Ck0y(0@K0o%17905{0vB0V30/10Ys1070E>08Z06605}0%f0Ao0M10GG0FE0B?0Bo0RU0Dr0qk0sc11(1O40Ja0O40GQ0CA0DX0T}0c{0UU0330FF0E+05L05<0W>0HT0cq08>0r,0ao0H80D[0Y!1NV0#r0AO0J@1CE09p02M06C0ME0uJ03W0Lq03n0S<08*0750V%0QK0Bq0TR0N^06B0My04<0VY06<0m=0Ug0Dk0bP1IK0Hz06(0MA06Q0B408G0Yw0n108i0Kw08C0)/0)c0L]0!#11V0D:0@k0I70O#0HV0JW0mO0s20%H09/0TB1830}n0KH0OD0Df0Ab1Ie0o20(A0aT1T&0WI0AT04j0i!1d90Pa09%08#0DF0GA08;0H40WG0Zn0O$05h0O80:{06>0eL0LK04J05c0N!0Kl0860D'09S07?0J#0IO0^K0M'0Uh0jc0KD0NQ0jL0q*0(/05W0v&0[z0bR0hP0}u0dp0%O0w'0Ye02w0m006707C0A,015R05i0BM0A$0PG0{D1Rf05'0Vz0J30oC13z03j03C0830N*0Bl0I404$0I(04&12D0Dv03Y0Fx02G03w0H+0M,0h30N?0Ze0,m0ZP0WP0Rg0X00uC0M]0P/07h0cv0F30e51fg1Br0DC0hQ08706H0L707l0$00Hy04)0K40460Jj06R08z0fo02102J01s04:08$0Ed0RG1Xw0Gw0Q31Qk0b00Ik0PA0Kx0^'1mX1Se1Cf0?+0ii1170h>0hS0C@04^0Ub0Yl0EZ0610Q50DG0nV0ER0N}0DD0Fy0Dw0+50UC0Ar0Gp0*)0NK0Eg0fF03H0490W(0HN0p]08H05M0{20880D(06v14M0BB0A00Sa0QW0En0iB02a0LO1W,1480TS0MF0I)0f(0MJ0&F0G,0Fh0nL0?&0cx0i60lQ0F?09q0dD0J$0N<0b90ia0RD0Xu0jA0^(0As0ND0JF0)@0cA0I@1VE0RP0Jw0N00G20T=0IJ0K=0O10NG0EC0GD0Q002h0BJ0nW0f=0Ea0R=08)06a0Dg0N30qR1iT07z0J+0Lr0760Mo03c0MP0D}03k0MR0H10Mc0Td0aj0C:0Hs0c%0LF0Rq0MB0kE0a>0PO0V@0XH0b10XN0mr04(0Mz05%0Dh05d0:?0B$0E80BW0N70Dp0d>0i(0LH1TT0xs0JP0Tj0ZE0^,0F>0Li0YT0M!0Kp09w0SB0Sz05!0C%0%>0Gz1T$1O30bB0H;0B#0J]08U0Fi0G&0F^0V^05U04b09X0B>0=60380Jn0PL0RT0@f0L=0N(0cP0qo02}0u^02/0Ky0R!01[03P0b20VI08(0M{0aa06w0lL0vn0Qz07b0AA04s07t0FA07c09P03u0N801f02d0JS02004A0D50E;06[0f>02i03>0A[0U80h60420Fj0370f^0p40Hc0M502K0a!0A*0AK05,0560a$01)0AB0JK08I0am0J:0DY08r0I=0300A]04t03D0Uz0Od0Dd02[0C*0AW0>z0Cd0K*0Tg03o0g>0dX03r13w0HQ0}s0GE03x0QL0Y30PY1$o03Z0Cm0o^06?0Us0Y40nl0Qs08[04+04%0Ir09k03Q0FN0Ny0Rc0Pe05b02!0DH0Yf03I0Iy01>0]j0F(0LB07/02S0Yp01!0hI0V/05z09{0zg07W03!05H02A0250Eh0CH06f0x=03$0Mf05/0CN0H^0EX0BE0M<04W0B50DV02,04m0F;03L08s02l0J50ay04!0Mp01@0B@0G*0Jv08:0N904S06806S0R^0KN0BF09U0EH0iW02D1Lm02x0CE0c(0LY07K06b02Q04i0P#06V0kx0@$03?04?0Bw0C}05v04n0I50#+0]F0'v0M)09x0EB0U%02@0At09v08403A02f0BX06!0H?0Hp0G@0KZ0YF0Nd02j04C0MK0JA04M0AD0H*0Nn01q01?0BN05X0Ac07105>08'07]0BK1az0x{0LU1iy0290bL03E0lR02%0Dz09C0Q,07R0q(0V701+0C#04E0G50In0Lj05&06I0f;02b02X0UK0Is07L03/01j06c0PP0Pi0JD07;05N06J08e02203)02z0520N=0B002309V02V02Y0Bs0FQ05j1Z=04@0770Cp0hv01X0^q0t>0A=0980A604F0A709Y08w0E=03s0E!0G008!0Tl0Fm1pp0io0F=0kY0Oj01y0Qu0F)02;0CQ07i0AF03}0OI0KE0R$0Nt09>0dK0720F50TL07>04d03B0#s12O0GZ03%0BV1A102*09r02g09+0FU0]c06K04k0GP03V0Ap02q0D]0FR0No07m04N0N203F0J*01'0p/0H(0j#09c0!/0&}0FB0tE0qu04c03t0M20VP0511g60%#0^<04O0AP0Fb0W70M?0Hu0CR09I0Uc0cZ0d30HY0Bd08f09l0LL0Ku0It0fw03#04X05m03;05O0Nr0Dl0Jt03@0Ne09m09d03d08}05n07:0(<0L/01h02:08a01#0K<06D0Bx0Ch0!a01k09003102L08j09W0Qr02^0M^0K>08%05R0%*09g03+08o02$0H=0T!03e06O0EQ05[03a06^07308Q09Q07D13p0Ay02R0BS0JM0FS0Db0I{02E0BY02?04e0Av0MX0Xf0L&02n0XC0GR06/02r03f0lI07v0HO06L0t?0C^0H,0D70U10NN0Ex04o0690S^04>03J0A102<0P70C<0BO0CI01x0G105A02+0a?08Y04h1kF0XA0t20bj0E?0o?09h0B+0DQ0Cw0940NX0%:07{01n0J(0Cl0De05s0Fc0X*0[#03M09&05P02u0Ry03407w0lu07E0LG03{0AI0GB06F0as0Fz01w02N06p0LP0530Qm0JN0Du0Te01$02'03S0BG0Be08D0CZ0HB0IT0K(0QX05k08g0&h01<0dH09=05I0Gx03[06+02F02s05?05w05^0S01Nd04'0Eo0on0OE0I00D20Cx0Ok03U0fM0aN0Ud0cQ0Zf08^0Dc0B(08<0JG0Nj09!0Pp03^0FJ0A>0EE0Ce0YP0Gj0oy0M+0FM0S%0KK0CU0fm02H0&m02#06q07+0A;01G0O<05:0Bv0F405e05x0Az06g0bc0OK04H0R60Gq0g^0M60Y{0Qh0I/0dE0Yt06&0Ad0D{04U0dc0C(06=0IQ06l0Ae0H)09s2A?05l0Z&05B0Dx0K{05+0Md03:0Lb0Jq07r09D0KL0HW0Bp0AX05@0E102O02B03=0ID0Mi0Rm0Q%0S:0T00fV0T50Wf0A%06X0L302=0HE07@0A{08/0Fn0G60I}0R#0J'0gC0H&0QY0R707'0Y=05;0g60C/03b0S#08+0Et02(09e04P0El0A/0Ng09'0U)02601b06]0J{03p02T04q04[0I+0Iw02y0BP06E03R03K0I80D=05]0Rp0X{0Y@0+?0Y}0ck02c04Y0OM03O0Ei0F[06Y0ES07n09*0Y*0{704008R0!e0R;09F07!0B30Qi0Cq0eX0{c0Sr0KO0P^0P)0Sh0Wa0a:0Fk02Z0[Z0BQ0AC0L!0DN0Q:0Xb0gK0Qe0ah0cs0Nz07F02m0D$11B0CS02p0R002U0ac06207d0kk0t@0qj0sh0rv0!b0Aw0Fo07A0NO0A+01P0BT01o05Y0DS0K)0W;0CB07X06'04p02&03T04r07M0G=04406m04#0K:07I0Bb04,07j0630Ym0Y50Z60Ri0W10Z]0c:05=07a0B=04]03<0Ow03]01t01;01c01^0Rb04y07o04*0KA0640Ct05J0K!0S]0Pr0L<0KI0Ss01l07S08p0A!0A^0G[07x07=0450RX0KP0OU0GJ02P02702801p0CC02I05C01H04{0AQ08b07e01I03N01:01&05t0A?02e01Y01}05G02W01g03507Y0l70hW0pI0d$0Z!0b+0jW0bs01u01%01r01m03v01R01M03l03&01]01N01Z01v01{01a01O01K01J01d01Q01=01i01z02C02k01,03g04B01L01T01S01V02>01W01/01e01U01* \ No newline at end of file +@infinite-storage|无限存储 (Infinite Storage) +ocelotsignmod@|迷上城建:豹猫指示牌 (Mishanguc: OcelotSign) + + +tianjin-metro-mtr3-adapted-edition|天津地铁 - MTR3适配版 (Tianjin Metro - MTR3 Adapted Edition) +personaldimension|个人维度 (Personal Dimension) +@dragionns-stuff +mmr-moogs-mineshafts-reimagined@ +lightsaber-reforge|光剑:重铸 (LightSaber Reforge) +by-powder-and-steel@|硝烟与钢火 (By Powder and Steel) +simple-gravel-ores@|沙砾矿石* +auto-torch-skr|自动插火把* +appleskin-numerical-edition|苹果数据(数字版苹果皮) (AppleStats) +factory-gauge-enhance@|机械动力:工厂仪表增强 (Create:Factory Gauge Enhance) + +breezes-settlements@|万家烟火 (Breeze's Settlements) +@vajra-renewed|金刚杵:延续 (Vajra Renewed) +singularity-iteration|奇点迭代* +girlfriends@|女友* +magic-brooms@ +dungeons-libraries-port +cosmere@|三界宙* +sable-ragdolllib@sableragdolllib|物理布娃娃库 (Sable: RagdollLib) +sable-maidragdoll@|女仆布娃娃 (Sable: MaidRagdoll) +maidtelepathic-ragdoll@|电波女仆:装死 (MaidTelepathic & Ragdoll) +@em4es +@wuzhongshengyou|无中生有:神赐重制版 (Ex Deorum Reuse) +@player-locator-plus-reforged +@alterna +modular-backpacks@|模块化背包* +mace-skyfall@|重锤:天陨 (Mace: Skyfall) +advanced-memory-card@|高级内存卡* +@moretetrabench +arcanemag@|奥术弹匣 (ArcaneMag - TaCZ × Iron's Spells Addon) +@vercte-satchels +sniffers-sniffs-delights +alexs-caves-mobs-accurate-mob-spawn@|Alex的洞穴:成倍生成 (Alex's Caves Accurate Mob Spawn) +glowstone-wire-reforged +scalingguis-reborn|界面缩放:重生 (ScalingGUIs Reborn) + +instantly-destroy-tree-clear-path-for-horse-riding|骑行秒清树 (Instantly Destroy Tree: Clear Path for Horse Riding) +toastsflyingtools|吐司的飞行工具 (ToastsFlyingTools) + +mo-creatures-revival +more-attributes@|更多属性 (MoreAttributes) +iepowerpackfix|沉浸工程电容背包兼容 (IE Powerpack Fix) +@toads-better-straw-beds +propeller-hats + +protection-engineering|工程防护* +@legacyforgej8 + + +schematica-plus@ +only-tp@|只能TP (Only TP) +shadow-drop@ +@no-fire-tick + +@pack-folders +@smooth-hover +@effortless-extract|探囊取物 (Effortless Extract) +meds-and-herbs-reborn@|医疗与草药:重生* +charm-of-undying-reborn@|不死图腾槽位:重生 (Charm of Undying: Reborn) +ichun-gravity-gun-mod-revival + +alexs-caves-neo +alexs-mobs-neo + +drzharks-magic-carpet-mod-revival +boss-companions@ + +tips-extension@|Tips 扩展* +wanderers-campsite@|流浪商人营地* +touhou-little-maid-crimson-heart-covenant|车万女仆:绯心待契 (Touhou Little Maid: Crimson Heart Covenant) +sable-ragdoll-patch@sableragdoll-patch + +command-block-studio@|命令坊块* + + +mbs-moogs-bountiful-structures@ +@mca-tacz-compatibility + +little-maid-harvest-helper|小女仆丰收助手* +touhou-death-sound@touhou-death-sound-mod +cloudworks-api@|沉云工坊API (CloudWorks API) +@stronger-armored-mobs +xiaofulans-secret-alchemy|小符兰的秘密药剂 (Xiaofulan's Secret Alchemy) +@lava-pockets-removed +precraft-accelerator@pcaprecraft-accelerator|前置合成管理器 (PreCraft Accelerator) +prospect@ + +@mekanismlightning-generator|通用机械:闪电发电机 (Mekanism: Lightning Generator) +@rs2ae_cell +eating-animation-fork@|进食动画分叉版* +saucelib@sauce-lib +@no-input-lag-tick-rate|无游戏刻输入延迟 (No Input Lag Tick Rate) +only-tp-violent-build|只能TP暴力版 (Only TP Violent Build) +xercas-tools@ +spell-trigger@spelltrigger|施法扳机* +magneticraft-reborn@magneticraftreborn|磁场工艺:重生 (MagnetiCraft: Reborn) +inventoryadvancementaccelerator@|背包成就性能优化 (InventoryAdvancementAccelerator) +projectef|等价交换Neo (ProjectEF Neo) +create-curios-backtank +mekanism-overclocked|超频 - 通用机械分叉 (Overclocked - A Mekanism Fork) +moonstone-gene-recombination@|月之石:基因重组 (Moonstone: Gene Recombination) +jfi@ +player-utils@|玩家工具箱* +cc-graphics@ +@trial-chamber-time-remover +rpg-enchantments +skeleton-marksman +blue-archive-halos-pyroxene@ +alexs-caves-refabricated@ +molecules-neoforge +butdays-blueprint|Butday的蓝图 (Butday's Blueprint) +battery-factory|電池工廠* +alexs-mobs-delight-legacy|Alex 的生物农家乐 (Alex's Mobs Delight Legacy) +ends-delight-legacy|末地农家乐 (End's Delight Legacy) +nethers-delight-legacy|下界农家乐 (Nether's Delight Legacy) +@legendarys-delight|传奇盛宴 (Legendary's Feast) + +sleepie|睡眠派* +abyss-sonata|深渊奏鸣* +proper-blindness + + + + +ftb-button-fix@|FTB按钮修复 (FTB Button FIx) +quantum-chromodynamic-charge|量子色动力学爆弹* +shulker-box-labels@ +agility-enchantment +veinminer-enchantment +pineapple-delight-legacy +brewin-and-chewin-legacy|饮酒作乐·上炕! (Brewin' And Chewin' Legacy) +twilight-delight-legacy +extra-delight-legacy|额外农家乐* +bosses-oddities + +@kaleidoscope-grilling|森罗物语:烟火 (Kaleidoscope Grilling) +goety-buffed-apostle +the-ultimate-raid +idealland-shrine|理想境-神碑* +lightning-rod-zombie@|雷霆之杖僵尸 (lightning rod zombie) +alone-host|一人成军* +@ourvillagers + +player-npc-weapons-expansion@annoying-villagers-player-npc-weapons-expansion + + + +create-resonance|机械动力:共鸣 (Create: Resonance) +@carryon-aeronautics-compat + +spore-serene-seasons-bridge|孢子 × 静谧四季/季节联动* +jasione@ +cave-dweller-reforked +food-ores-neo@food-ores|食物矿石:革新* +inf-team|信息团队 (Inf-Team) +fungal-infection-spore-infnexus|真菌感染:孢子 感染枢纽 (Fungal Infection: Spore Infnexus) + + +chat-canvas|聊天画布* +led-it@|灵光同步 (Led it!) +findme-salvation@findmesalvation|找到我:救赎 (Findme: Salvation) +cnpc-tacz-fire +@ftb-quests-background|FTB任务背景 (FTB Quests Background) + +radical-exp-strategy@|激进经验策略* +villager-trading-overhaul@|村民交易界面优化* +tinker-lens-curios-support|余烬传奇之丢失的眼角膜* +heaven-dimension-mod +@better-language|更好的语言 (Better Language) +@sable-vivecraft-fix + + +smileys-better-voice +laowu-meme@|老吴~* +icy-lib|冰洁柠檬心的Lib* +cnpc-epicfight-addon-ce +mythical-creatures-reborn@|神话生物重制版* +compactwaterturbines|压缩水轮机 (CompactWaterTurbines) +@better-footage|更好的录制素材 (Better Footage) +cnpc-plus-fix +lenstrack|追镜 (LensTrack) + +icy-punishing|Icy战双帕弥什* +i-know-my-effects +hall|腐朽之疫:王庭 (Rotten Ruins:the Hall) +caustica@ +carpet-fga-addition@ +shanhaixiantu|山海仙途3 (ShanHaiXianTu) +@permissionmanager +emf|异常管理基金会 (Exception Management Foundation) +piglin-outposts@|猪灵营地* +divine-workshop@divinework-shop|神咒工坊* +galosphere-spelunkery-compat@ +epca-visionfix|EPCA 适应视觉修复 (Epca‑VisionFix) +godsick|创疴 (GodSick) +the-fall|神陨传说 (Fallen) +destructive-spelunking +datastats|数据统计 (DataStats) +botania-backpack-upgrades@|植物魔法背包升级* +@chat-optimizer|聊天优化 (Chat Optimizer) +@map-chameleon|地图变色龙 (Map Chameleon) +@create-embedded-freights-fly|机械动力:嵌入式包裹飞越 (Create: Embedded Freights Fly) +piston-scanner|活塞扫描仪* +@create-ccbr +infectioncoreframework|感染核心框架 (InfectionCoreFramework) +create-petrochem@ + +@villagertradestats +tube-transport-system-reforged|管道传输系统:重铸 (Tube Transport System : Reforged) +@commonprosperity|经验共享 (Common Prosperity) +cnpc-npcpathfinding + +rebindnarrator-fabric@rebindnarrator¨rebindnarrator +@create-labor-rush|机械动力:劳工冲刺 (Create: Labor Rush) +create-flight-control@|机械动力:飞控学 (Create: Flight Control) +aeronautics-interstellar-expansion@aeroie|航空学:星际扩展 (Aeronautics: InterStellar Expansion) + +viscriptquests@ +@floodgate + + +nolando-apocalypse@ + +extended-mining-tiers-jade-compat|扩展挖掘等级:Jade 兼容 (Extended Mining Tiers - Jade Compat) +apothic-attributes-whitelist|神化属性白名单* +@lots-of-harm-visualization|好多伤害可视化 (Lots of Harm Visualization) +the-hybrid +dimensionpotion@|维度药水 (Dimension Potion) +walking-cane|步行手杖* +mobmind|生物心智 (MobMind) +terrafirmacraft-immersive-gardening|群峦传说:沉浸园艺 (TerraFirmaCraft: Immersive Gardening) +tfc-scraping-knives|群峦刮刀* +ae2-autocrafts-thaumcraft-and-botania +better-config@ +sodiumleacculling-unofficial@sodiumleafculling-unofficial|钠:树叶剔除-非官方版 (SodiumLeafCulling-Unofficial) +mo-bends-reforged|更多弯曲:重铸 (Mo' Bends (Reforged)) +excavar@ +net-music-better-loginfa@|网络音乐机:更好的登录FA (Net Music: Better Login FA) +emi-swift-copy|EMI 迅速复制 (EMI Swift Copy) +super-factory-manager-gui +hunting-quest|狩猎任务* + +nofireworkfly@no-fire-work-fly|无烟花飞行 (NoFireworkFly) + +better-smithing-template@|好锻!* +@reveal-index-codex|辑录与揭秘 (Reveal: Index & Codex) + +el-and-ls-dyes@el-and-ls-dye-mod + +more-strong-commands|更多强力命令* + +bakeries-simulation@|烘焙坊模拟经营* +ex-nihilo-abnormals@ +@glow-my-teammates|发光吧,我的队友 (Glow My Teammates) +ex-nihilo-additions@ +chinese-weapons@|中式甲兵* +@metaphysics-reset +openblocks-reborn|开放式方块:重生 (OpenBlocks Reborn) +portablestorage-rift@ +smelting-enchantment +@versiont +@mcpythonon +@s1-mtr-addon|S1的MTR扩展 (S1's MTR addon) +carpet-primaryuan-addition@carpet-pry-addition +no-singleplayer@|无单人模式* +legendary-delicacies|传奇珍味* +double-doors-continued@|双开门延续版* + +@boats-is-jumping +brilliant-text +legendary-monsters-tetra-material|传奇怪物:Tetra兼容* +kaleidoscope-nether-refabricated@|森罗物语:下界——重织* + +bird-watching-community-ports|新·观鸟 (Neo Guan Niao / Bird Watching - Community Ports) +luckperms-gui-nametags +@the-roads-more-travelled +@killblock|杀敌方块 (KillBlock) +@ciallominecamera|你好,我的相机! (Ciallo Mine Camera) +serverwarashi|座服童子 (ServerWarashi) +bamboo-tweaks +be-beryllium|铍 (Beryllium) +create-track-switch-radio@|机械动力:转辙机无线电 (Create: Track Switch Radio) +@launchers_and_arrows + + +@isocraft +gtnh-external-lib + +toilet-technology|厕所技艺* +immortal-cultivation@immortal-cultivation-mod|修仙之路* +tfc-punishment-for-death@|群峦死亡惩罚 (TFC Punishment for Death) +liubai@|留白* +impirium@ +@playeruploadermod|上传玩家位置 (Player Uploader) + +@veinmine|连锁挖掘 (Vein Mine) + +mishanguc-better-colortool@|迷上城建:更好的颜色工具 (Mishanguc: Better Colortool) +lm-addons@ +kubejs-cuisine +create-diesel-generator-unofficial-fork-for|机械动力:柴油动力 - 非官方分支 (Create Diesel Generator - Unofficial Fork for NYfAxSMP3) +create-bits-n-tracks@ +irongolems-life|铁傀儡人生体验 (Iron Golem Player / IronGolem's Life) +tacz-caliber-ammo|TacZ:火力重塑 (TacZ: Caliber Ammo) + + + +@maid-tavern|女仆酒馆 (Maid Tavern) +super-flat-core +oritongs-diamond-core + +@just-enough-accidents|险兆备份 (Just Enough Accidents) + +lr-tactical-1-21-1@lr-tactical-1.21.1 +rs-integration@|共振存储 (RS Integration) +create-ages + +roguelike-dungeons-arcana-edition + +rangers-apprentice-mod@rangers-apprentice|游侠学徒 (Ranger's Apprentice) +omni-upgrades@|全能升级* +iron-chest-fix@|更多箱子修复* +tfc-lunch-box@|群峦午餐盒 (TFC Lunch Box) +returnfeatures +@etherstweaks +blockshin-menu|方神菜单* + +@void-power|虚空动力(重置版) (Void Power Reset) +@classic-rblx-tools +blockestra +new-collisiondamage@|碰撞伤害:重置 (New CollisionDamage) +avariria-progression-expansion + +hostility-staff@|敌意之杖* +chocolatiers-delight +farmers-spell@|魔法食事 (Farmer's Spell 'n Spell Book) +@libr-getter +uei@|通用附魔信息 (Universal Enchantment Info (JEI/REI/EMI)) +tacz-auto-fire|TACZ:自动射击 (TACZ: Auto Fire) +universal-container|通用容器* +moremaidtask|更多女仆工作模式 (MoreMaidTask) +thunderbolt-core@ +@living-item|活物品 (Living Item) + +worldhistory|寰世忆影 (WorldHistory) +irons-spells-3d-cast-preview@|铁魔法法术3D预览 (Iron's Spells 3D Cast Preview) +tconstructs-loli|匠魂萝莉 (TConstruct's loli) +litematica-print|更智能的投影打印机 (Litematica-Print) +evolveequipment|更多升级装备 (EvolveEquipment) + +ds-neck-fix-paperdoll-compat|龙之生存:纸娃娃兼容修复 (DS Neck Fix - PaperDoll Compat) + +@smoothcamera|平滑相机 (Smooth Camera) +touhou-maid-eat-properly|车万女仆:好好吃饭 (Touhou Maid: Eat properly) +chiseled-bookshelf-searcher|雕纹书架搜索器* +@sable-quota|Sable:配额管理 + +@daydram-glittering|浮世幻梦 (Daydream: Glittering) + + +brilliant-text-remixin +flashlightmod|手电筒 (FlashlightMod) +showcaser@|展示器* +@ciallo-mine-datapack|你好,我的数据包! (Ciallo Mine Datapack) + +@cavebiomesapi +mbts-mobs|Mbt的人形怪 (Mbt's Mobs) + + +tree-chopper-continued|砍树延续版* + +@worldgen-devtools|世界生成开发工具 (Worldgen Devtools) + +@carpet-ice-addition|冰毯 (Carpet Ice Addition) + +ala-industrial@ + +greglink|格雷桥接器 (GregLink) +@automaticcrafting|自动合成台 (Auto Crafter) +@the-final-boss +touhou-little-maid-love-loathe|车万女仆:爱憎分明 (Love & Loathe) +entity-modifier|可视化实体修改器* +zettas-spells +dimension-leak-fix@|维度内存泄露修复* + +@manhunt-wildcard|猎人游戏-外卡 (Manhunt Wildcard) +visor@ +jdte +dog@devourer-of-gods|神明吞噬者 (Devourer of Gods) +alexs-caves-fabric-neoforge-port +bob-the-toolbox + +fastgelica +cnpc-enhance + +@tlm-maid-survival|车万女仆附属:女仆生存 (TLM: Maid-Survival) +dnz-skibidi-toilet@ +@input-method-restore|撤销输入法锁定 (Input Method Restore) +@trashslot-mite|垃圾槽 - MITE (TrashSlot - MITE) +@trash-slot +@inventory-crafting-stick-standalone|便携工作台 (Inventory Crafting Stick) +jasons-disenchantment-forge + +flexibook|自定义书 (FlexiBook) +pu-dragon|钚龙 (Plutonium Dragon) + +@listmore + +better-amethyst-plus@amethyst-plus|紫水晶拓展 (Amethyst Plus) + +launchpad@ +tfcreate-compat@ +show-me-your-skin-neoforge-port@ +anvilcraft-slugplus@|铁砧工艺:蛞蝓+ (AnvilCraft: SlugPlus) +ae2-uelm +@fluxnautics +autofish-for-fishingoverhaul|自动钓鱼XPlus版 - Fishing Overhaul 兼容 (AutoFish for Fishing Overhaul)¨@autofish-for-fishingoverhaul|自动钓鱼XPlus版 - Fishing Overhaul 兼容 (AutoFish for Fishing Overhaul) - 正在 +@miehex|咩之咒法 (MieHex) +@miehexrevolution|咩之咒法:变革 (MieHex: Revolution) +fruits-delight-legact|果园农家乐 (Fruits Delight Legacy) +neopasterdreamsanity|新帕斯特之梦-乱 (NeoPasterDreamSanity) +neopasterdreammeltdream|新帕斯特之梦-融梦 (NeoPasterDreamMeltDream) +neopasterdreamspells|新帕斯特之梦-咒语 (NeoPasterDreamSpells) +apothic-enchanting-vanilla-integration|神化附魔原版兼容* +re-avaritia-armor@avaritia-armor|无尽贪婪:重生:盔甲 (Re:Avaritia: Armor) +vintage-tech +hydrofarm@ +@clean-keystrokes +player-beacons|玩家信标* +enhanced-celestials-2-default-lunar-events@|月亮事件 2:默认月亮事件 (Enhanced Celestials 2: Default Lunar Events) +horde-moon-2 +dynamic-trees-pale-bloom +dramatictrees-palebloom-cr@ +blade-tetra-forged-styles|拔刀剑:百构流派 (Blade Tetra: Forged Styles) +project-souls@ +placeable-gaze-manual|可放置越幔全典* + +enhanced-celestials-2-shader-support@|月亮事件 2:光影支持 (Enhanced Celestials 2: Shader Support) +contract-blade|车万女仆:契约之刃* +@fabric-custom-names +ars-nouveau-combat-glyphs +@thip-mm|特摄英雄补完计划-城市超人 (THIP Metro Man) +@homelander-lasers + +unofficial-tacz-refabricated|永恒枪械工坊:零 - Fabric(非官方) ([UNOFFICIAL]TaCZ Refabricated) +sign-pack@the-sign-pack + + + + +gtnh-lotr-nei@gtnh-nei-lotr|魔戒NEI插件:GTNH 版 (GTNH LOTR NEI) +enhanced-celestials-2-core@|月亮事件 2:核心 (Enhanced Celestials 2: Core) +peaceful-moon-2 +hyperslide@ +@outln.frombelowland +nei-resources@ +pama-mobs@|PAMA模组 (PAMA-mobs) +@modernobscure|提示框杂烩 (ModernObscure) +dimensions-of-alexs-caves-jei-integration@ + +ld|传奇珍味NeoForge版 (Legendary Delicacies NeoForge) +harvestgalacticraft +biomas-vanilla-renovados@ +grapplemod-skybound@ +dpsmeter|秒伤统计面板 (DPS Meter) +pulse-trims@|脉动纹饰* + +@personal-anti-cheat|反个人作弊 (Personal Anti-Cheat) +ironfurnacesfix|更多熔炉修复 (IronFurnacesFix) + + +@hydro-agility +reimus-initial-dimension|灵梦的初始维度 (Reimu's Initial Dimension) +ae2-crafting-cpu-item-count-display|AE2合成CPU物品数量显示 (AE2 Crafting CPU Item Count Display) +dynamic-trees-regions-unexplored@dynamic-trees-ru|动态的树:未至之地附属 (Dynamic Trees - Regions Unexplored) + +dragon-ars-bridge +skip-title-screen|跳过标题画面* +f-tech@ +@tfc-info-integration|群峦信息显示 (tfc info integration) +highlands-for-all +homeostatic-seasons@ +ae2-additions +survivaltoolbox@|生存工具箱 (SurvivalToolbox) + +loot-box|战利品箱* +autoemc@ + + + +team-view-relay@ + +chatorder|聊天点单 (ChatOrder) +tactical-melee-attribute-compat|绿葡萄战术装备属性修复* + +streams-reflowing@|溪流:重流* +runfast|迅步疾行 (runfast) +ender-trigon-unofficial|末影三头龙:非官方版* +enderman-xtra|末影人:恶意 (enderman xtra) +legend-star@legendstar|传奇之星* + +kilagraph@ +@enchantment-book-of-undying +the-reverse@reverse_mc|重返MC (Reverse:MC) +axon-technologies@axontechnologies¨redthunderaniummod@axon-technologies + +cybernetics-cyberchems@ +heavyspear@|重矛 (Heavy Spear) +@item-texture-exporter|物品纹理导出 (Item Texture Exporter) +ae2ud@ +stellarcreateoptimization +create-coasters-simulated@ +freshness|新鲜度* + +@create-ore-excavation-deposits +fluorite-the-realm-of-dreams + +@ghastling_sound_fix|小乐魂声音修复 (Ghastling Sound Fix) + + +neoloading-vulkan-fix@ + +pehkui-rebuilt@|Pehkui 重构版* +ars-arcane-matrix|奥术矩阵* +trade-stages +tradestages-legacy +refart|响屁不臭* +@everlatweaker|言引定制/七叶镜绫的修改 (EverlaTweaker) +a-wish-yet-to-bloom|未竟花愿 (A Wish Yet to Bloom) + + +chinese-horror-bride-in-red@chinese_horror_bride_in_red|中式恐怖-红衣鬼新娘* +bing-agriculture-extend|冰·农业工具扩展* +food-vendor|美食小摊* +@probability-pattern|AE2 概率样板 (Probability Pattern for AE2) +cave-dweller-overhaul@ +tinkers-metallurgy +bbs-fs@ + + + +anvilcraft-pigeon-plus@anvilcraft-pigeonplus|铁砧工艺:鸽+ (AnvilCraft: Pigeon Plus) +the-goetyard +@tacz-bleeding + +ragdollified@ +fractured-bones-and-more-fracturedbones|骨折 (Fractured Bones & more) +@empiremod|帝国 (Empire) +forolite + +auto-seam-blend@|自动连接纹理* + +carpettngaddtion@ +@seed-map-for-xaero +botania-overhaul +raithaumcraftadditions@ + + +ars-noita-addition|更Noita的新生魔艺* +slashblade-a-belated-gift|拔刀剑:迟来的礼物 (SlashBlade:A Belated Gift) +resourceignorer@|资源加载忽略 (ResourceIgnorer) +item-cleaner-cat|清理喵* + +cobblemon-droploottables@ +create-green-print|机械动力:绿图 (Create: Green Print) +history-stages@|历史阶段* +libvulpes-continuation|LibVulpes - 延续版 (LibVulpes - Continuation) +advanced-rocketry-continuation|高级火箭 - 延续版 (Advanced Rocketry - Continuation) +xaeros-maps-multiplayer-plus@ +item-get@|物品提醒 (Item Get!) +legacy-hellfire +cdmlloader +ocelot5836s-craytunes +extra-ons +super-mario-world-application +bubble-burst +kabores-discord-application +device-mod-apps +apps-mod +spyeedys-shop-app +calculatorplus +model-creator-app +smartqueue@ +hachimis-buoyancy-machine|哈基米的浮力机 (Hachimi's Buoyancy Machine) +answertime|答题时间 (AnswerTime) +legacy-textiles + +hephaestus-forge + + +tester-mod@|测试者模组* +ae2-batchcraft|AE2 批量工艺 (AE2 BatchCraft) +multi-function-trash-cannon@|多功能垃圾炮 + +mc-mods-crash@all-crash|通通崩溃 (allcrash) +millager@|戍村卫民 (Millager (Village Defenders)) +modularshoot@|模块化射击 (ModularShoot) + + +emoticons-reforged|动作表情:重铸* +jei-tag-groups|JEI 物品配方折叠 (JEI Tag Groups) +bettergamerules|更好的游戏规则 (BetterGamerules) + + +mine-slayspire@|杀戮尖塔2 (Mine-SlaySpire) +touhoulittlemaid-mimo-tts|车万女仆:MIMO语音合成支持 (TouhouLittleMaid MiMo TTS) +@amatweaks + +tctb|匠拉 (TConstruct-Tetra Bridge) +modtweaker-community-edition +@pretender|伪装者 (Pretender) + + + +jei-trade|JEI交易 (JEI Trade) +enchanted-outlines@|轮廓即宣言* + + +@spiritingaway|应隐待捉/八云紫的神隐 (Yukari's Spiriting Away) + +custom-recipes-editor|自定义配方* + + +legendary-farming@ + +pipez-optimizer@|Pipez优化器* +tinkers-planner-antique + +voltex@ +horizonui@ +collapsible-groups + +@pipez-filter|Pipez 过滤升级 (Pipez Filter) +achievement-trophies|成就奖杯* +@charliezoom|Charlie 放大 (CharlieZoom) +ae2-quick-calculation|AE2快速计算 (AE2 Quick Calculation) +create-extend-extendo-grip|长臂猿,现在就在你家门口! (Create: Extend Extendo Grip) +@yourstageskybox +real-night|真实黑暗 (RealNight) +twilight-aether@ +@ivars-bosses + + +buildcraft-community-edition@|建筑社区版 (BuildCraft Community Edition) +construction-wand-1-12-forge|建筑手杖 1.12 (Construction Wand 1.12 Forge Porting) +mcgs-questborne@ +copper-furnace|会氧化的铜熔炉* +zifeng-s-custom-skill-tree|子枫的自定义技能树 (zifeng_s_custom_skill_tree) +modularshootammo|模块化射击:弹药 (ModularShootAmmo)¨@modularshootammo|模块化射击:弹药 (ModularShootAmmo) - 暂未过审 +icy-iron-spell|冰的铁法术魔女* +ocelotsignyunbei@|云北城建:豹猫指示牌 (YunbeiUC: Ocelot Sign) +emcmap-addon@ +universal-block@|通用格挡* +ore-sheep-mod +metal-wings@metalwings +tacz-mesh-loader@ +@chromatic-aberration-mod|色差 (Chromatic Aberration Mod) +improved-villagers-organized-combat +supreme-gold|至尊金* + +time-bus|时间总线* +farmers-future-delight|未世之乐 (Farmer's Future Delight) +spartanapothiccompat@|斯巴达武器神化兼容 (SpartanApothicCompat) +jinzai-traffic-lights@|津仔的交通灯 (JINZAI Traffic Lights) + +modular-muskets@ +penetration-enchantment|洞穿附魔* +herowolf|豪狼 (HeroWolf) + +perspective-api@|视角 API (Perspective API) +gregtech-universal-frame +@lost-soul|传说之下:离魂惘途 (Lost Soul) +mianbaos-newmodernwarfare|面包的新现代战争 (Mianbao's NewModernWarfare) +no-bedrock +serverlogin|服务器登录 (ServerLogin) +collections-of-optimizations +one-night-to-die|一夜致死* + +beyond-the-bedrock@ +neoterraforged|新:地形重锻 (NeoTerraForged) +@fooddelivery|外卖系统 (Food Delivery System) +constructum-tech|构元科技* +tinkers-strainer|匠魂滤网 (Tinker's Strainer) +gears-of-valor +ftb-project-ex-1-7-10-backport|等价交换升级(非官方移植版) (Project EX (Unofficial Backport)) +alexs-caves-up + +celestisynth-enhancement|Celestisynth 武器增强* +create-cats-dont-care@ +create-re-packager-fix@|机械动力:理包机修复 (Create: Re-Packager Fix) + +smart-zombies-mobs|僵智* +trade-preset@|交易预设* +bettersmithingplus +tacz-gun-damage-regulator|TaCZ全局伤害调节 (TaCZ Gun Damage Regulator) +jei-plus-plus@ + + + + + + +ae2-quick-encoding|AE2快速编码 (AE2 Quick Encoding) +ae2-quick-encoding-upload|AE2快速编码上传 (AE2 Quick Encoding Upload) +delta-chunk@deltachunk +@bilingual-tooltips|双语灵动岛 / 双语 Tooltip (Bilingual Tooltips) +miners-delight-bridge|矿工桥家乐 (Miner's Delight Bridge) +covenant-of-the-seven +vvvf-simulator-library@|VVVF模拟器核心库 (VVVF Simulator Library) + + +faster-scrolling@ +solo-quests|FTB任务独立 (FTB Solo Quests) +@preventbuild +rod-of-discord|混沌传送杖 (Rod of Discord) +create-meowchanics|机械动力:老吴学 (Create: Meowchanics) +@miniscaled-forge-unofficial + +modernica@modernica-fabric +enchantlib +gensou-works|幻想营造* +gtbcs-cataclysmic-boss-ui@ + +cnpc-bbs-integration@cnpcs-bbs-addon +create-advanced-frequency-network|机械动力:高级频段网络 (Create: Advanced Frequency Network) +mapart-helper@ + +leak-warning|厚里泄!* +netmusic-createstorage-compat@|网络音乐机 x Create: Storage 兼容修复 (NetMusic CreateStorage Compat) +bettervein|更好的的连锁 (Better Vein) +experience-durability@|经验耐久* +touhoulittlemaid-self-talk@|车万女仆:女仆自言自语 (TouhouLittleMaid Self Talk) +@khdgxnn|更新按钮 +extra-ons-2 +touhou-little-maid-maid-manager|车万女仆:便携管理 (Touhou Little Maid: Maid Manager) + +ring-of-the-hundred-curses@|玩游戏时在做什么?有没有空?可以加上百咒吗?/ 百咒之戒 (Ring of the Hundred Curses) +item-alchemy-expansion@ +plumes-summoner|羽光沉璧的召唤器 (Plume's Summoner) +@infinite_card|无限卡牌 (Infinite_Card) +kaleidoscopecookery-unofficial-26-neo-ver|森罗物语:厨房非官方26Neo版 (KaleidoscopeCookery Unofficial 26 Neo Ver) +mythika@ +kaleidoscope-extension@kaleidoscope-jei-extension|森罗物语:厨房JEI拓展 (Kaleidoscope Jei Extension) +dragon-survival-tacz-compat@|龙之生存:龙持枪?! (Dragon Survival TACZ Compat) +@tfc-auto-knapping|群峦自动打制 (TFC Auto Knapping) +box-of-structures-legendary-monsters + +maid-mining|矿透女仆* + +mr-yao-fantasy-creatures-mod|Mr.Yao的幻想生物 (Mr.Yao Fantasy Creatures) +@qed|伤害溢出修复 (Quite Enough Damage) +rfapi@|水库 (Reservoir Fluid API) + +ferric-oxide@|氧化铁 (FerricOxide) + +combatives@ +@enchantment-glint-outline|附魔轮廓 (Enchantment Glint Outline) +more-vanilla-shields@ + +@hunters-compass|猎人游戏-指南针 (Hunter's Compass) +resonant-synthesis@ +archaion + +yqils-arknights|YqIl的明日方舟 (YqIl's Arknights) +functional-statues@ +bloodmagic-akashic-records|血魔法:阿卡西记录 (BloodMagic:Akashic Records) +@threateningly-mobs-continued +yazzs-dungeons-the-sequel + +bbbbeacon|信标光束改良 (BBBBeacon) +modern-companions@ +@custom-playerdoll|玩家模型玩偶 (Custom Playerdoll) +physics-mobs-death@ +monster-frenzy|怪物狂潮 (Monster frenzy) + +macabre +thaumic-dabblery@ +@movablegui +@throw-it|万物皆可抛 (Throw it) +bomberbeast@ +abandoned-post-apocalyptic-buildings +bullets-boats@ + +dreamshift@ +tacz-project-pneuma +shape-shifters-compass|幻形者罗盘 (Shape Shifter's Compass) + + + + + + +smileys-better-brain +douyinview|游戏内抖音悬浮窗 (douyinview) + + +pams-harvestcraft-2-tweak@|潘马斯农场2修复 (Pam's HarvestCraft 2 Tweak) + + +productive-bees-genesis|资源蜜蜂·创世* +mob-crops +modularrarity@|模块化稀有度 (ModularRarity) +create-level|机械动力:等级 (Create: Level) +tinkers-assembly|工匠装配 (Tinkers' Assembly) +create-waterparked@|机械动力:水上乐园 (Create: Waterparked) +sleepingoverhaul + +@spreadspawnpoints|扩散出生点 (Spread Spawnpoints) +autofishs@|自动钓鱼S (AutoFishS) + +@blessingofpandora|潘多拉之祝 (Blessing of Pandora) +@suiyuemo|沉重系列 +ae2-addon-infinite-expansion + + +better-sparse-structures +goety-spell-source@goety-spell-source-api|诡厄巫法:法术源 API (Goety: Spell Source API) +magical-psi-revival@ +pact-curios@|平等契约 (Pact curios) +advanced-mob-leveling +signedit|告示牌编辑 (SignEdit) +jei-enchantment-info-1-12-2 +chat-heads-vintage +anvilcraft-doge-plus@anvilcraft_doge_plus|铁砧工艺:Doge+ (AnvilCraft Doge+) +steam-powered-create@create-steam-powered|蒸汽动力(非官方) (Create: Steam Powered) + + + +tfc-more-cans-and-jars@|群峦更多罐头 (TFC More Cans and Jars) + +@rgb-chat|给你点颜色瞧瞧:文字版 (RGB Chat Reborn) + + +poof + +chunk-plan|探索限额* +@rebetterend + + +htmlcraft-api@ +polymer-neo +create-gt-compatibility-of-boards|机械动力 : 格雷科技的材料制造兼容 (Create : GT Compatibility of boards) +blocks-toukenranbu|方块与刀剑乱舞 (Blocks & Toukenranbu) + + +leapcraft|越级合成 (LeapCraft) +fantasy-pattern|AE:幻梦编码* +spice-of-life-stick-edition|生活调味料:木棍版 (Spice of Life: Stick Edition) +drop-confirm@ +notify-server-messages +@intelium-mod +cnpc-bettercombat +orcinvasion|兽人入侵 (orcinvasion) + +@superb-warfare-refabricated + +mca-quests@ +fish-n-ships@ +incision + +rift-gun|裂隙传送枪* + +sixseven@sixsevenstack +vivid-move|灵动! (Vivid Move!) + +deermod-by-arisuwumei@deermod +reterraforged-unofficial +black-knight-armor-and-food|冰火传说:黑骑士扩展 +field-guide-monster-checklist@field-guide-monster-check-list|Field Guide:里程碑提升 (Field Guide: Monster Check List) +fkferrouswroughtnaut@|去他的钢铁守护者 (FkFerrousWroughtnaut) +multigolem +neovitae@ +whatstheloot +hbm-mekanism-compat@ +@antideath-survival-assistant +focus@ +super-graphite-electrode|超级石墨电极* +@quickcraft-container-projection +hud-menu@|Hud菜单* +honeymilkbucket@honey-milk-bucket|蜂蜜奶桶 (Honey Milk Bucket) +ananke|命途未央 (ANANKE) +arcane-patternworks +tetra-insight +@create-schematic-compute|机械动力:蓝图计算机 (Create: Schematic Compute) +ftech-industry|异星科技 (FTechIndustry) +@microtimingreplay|微时序回放器 (MicroTimingReplay) + +polyglottooltip +@simple_chess|国际象棋 (Chess) +analyst|分析者* +masonry@tm-masonry|石工工艺* +winefoxmoreanimation|酒狐更多动画 (WineFox More Animation) +@ride-together-mca-immersive-vehicles +post-it@ + +@collect-everything|收集一切 (Collect Everything) + +@simple-farming-reworked|简单农业:重构版 (Simple Farming Reworked) +tastebud-delights|味蕾乐事 (Tastebud delights) +teyvats-delight-a-farmers-delight-add-on@teyvats-delight|提瓦特乐事 (Teyvat's Delight) + + + +structure-race|结构竞速* +creature-corner@creaturecorner +q-shop|企鹅商店 (Qshop) +@phayriosiscataclysm +the-last-sword-you-never-forgot@|最终之剑:重铸* + +inventory-item-groups@|物品分组* +ae2-recent-search|AE2 最近搜索 (AE2 Recent Search) +piglin-bartering-for-jei@ +thaumcraft-aspect-annotations|神秘时代要素注释* +rs-search-history|RS 历史搜索记录 (Refined Storage Search History) +carpet-dds-addition@ +hps-portable-workstations|HP的便携式工作方块 (HP's Portable Workstations) +@bulletproof-armor|防弹衣 (Bulletproof Armor) +flower-tea|花茶* +not-so-shrimple-reshrimpled@not-so-shrimple-(reshrimpled) +z-core@|Z-核心 (Z-Core) +crabbers-delight-upgradable-crab-trap +villager-trading-station@|村民交易站* + + +@litematica-printer-hana|投影打印机 Hana版 (litematica-printer-hana) +modularshoot-panel@|模块化射击:插件面板 (ModularShoot: Panel) +depth-crawler-the-horror-of-exploration +avalonbase +matrix-speak-to-me@ +targetsindicate|目标血显 (Targets Indicate) +nineturnring|九转戒 (Nine Turn Ring) +compass-to-map@ +endsustain@|落幕终焉 (endsustain) +cobblemarket|方可梦市场 (CobbleMarket) + + +@dialogautologin +@bettersearch +wet-farmland-mod@|湿耕地 (Wet Farmland) +ancient-warfare-2-remastered + +flux-networks-ultimate-addon +@all-powerful_translator_mod|AI全能翻译 (All-Powerful Translator) +easy-magic-apotheosis-compat + +@unnofficial-port-apocalyptic-fortress +create-beyond-stock-fix|机械动力&超越维度兼容性修复* + +red-eyes@|赤瞳* +cat-idol|猫神像* +kaleidoscope-hodgepodge@|森罗物语:杂烩* +ftb-quests-container-integration|FTB检测兼容 (FTB Quests Container Integration) +the-village-fights-back@ +tgzjdvs-chat@|TGZJDV的聊天 (TGZJDV's Chat) +project-expansion-arcanum +rinku@ +pianolib +@twilight-iris-xe-fix +anti-spectator-noclip@ +not-so-hardcore@notsohardcore +es-esoteric-spells-irons-spells-addon|秘法法术 (Esoteric Spells - Iron's Spells Addon)¨@esesoteric-spells-irons-spells-addon|秘法法术 (Esoteric Spells - Iron's Spells Addon) - ,暂时无法正常 +entity-player-compat +gremlib@ +opencomputers-rebooted|开放式电脑:重启 (OpenComputers: Rebooted) +railways-untold@ +irons-artifice@|Iron的武备与巧械 (Iron's Arms 'n Artifice) +potion-coating@potion_coating|药水涂层* +itemphysic-guns@itemphysicguns +scannable-jei-compat +betterblockz-decorz@ +@better-multiworld +embers-delight@ +immersive-fixes-old-ie|沉浸工程修复 - 旧版 IE (Immersive Fixes - Old IE) +my-nethers-delight-fix@ +@kaleidoscope-donkey-meat-reborn|森罗物语:驴肉重生 (Kaleidoscope Donkey Meat Reborn) +cdna@ +@presence-footsteps-lite|脚步声 Lite (Presence Footsteps Lite) +@qqbot +dungeon-train@|地牢列车 (Dungeon Train: A Lore-Rich Roguelite RPG Adventure) +tacz-accessory-table|TaCZ配件桌 (TaCZ Accessory Table) + +findme-cobblemon|找到我:方块宝可梦 (FindMe: Cobblemon) + +touhou-maid-mine-together|车万女仆:一起挖矿 (Touhou Maid: Mine Together) + +@sable-fix-mod|Sable:复制修复 (Sable Fix Mod) + + + + +villager-inventory-viewer +dimensional-depths-rexplored +oneworld@|三界合一 (oneworld) +old-debug-rendering@ +falling-meteors-remaster +ae2-search-optimization|AE2搜索优化 (AE2 Search Optimization) +grotesque-steve-safety-edition +@starrailexpress|开拓星穹谋杀案 (StarRailExpress) +stoneycore@ +ic2-nuclear-external-cooling|核电外置冷却 (IC2 Nuclear External Cooling) +food-enchanting-table|食物附魔台 (FoodEnchantingTable) +xmly-enchantmentstooltip@ +kenshicraft +knights-and-heraldry@ + +@curiousmobs|万物皆饰 (Curios Mobs) + +irons-spells-x-electroblobs-wizardry-redux + +minecraft-dungeons-sands-temples +@ndpreforged-mod +device-lock|设备封禁* +itemban|物品封禁* +dysoncubeproject-addon|戴森立方计划附属 (Dyson Cube Project Addon) +@moemusic +drawing-board@|画板* + + + + + +simple-card-memo|简易备忘录* +massive-cells-ae2 +infinite-lava-cell-ae2 +tiny-cells-ae2 +custom-attack|自定义袭击* +create-buddycards@ +kindred@ +combateventfix@|战斗事件修复 (CombatEventFix) +strata-rock-bursts +@coordinate-record-book|坐标记录簿 (Coordinate Record Book) +sophisticated-atm-tiers +apothenchantmentmenu-java-apotheosis +more-create-burners-with-ember-burner@ +ntge-bobfluidtranslator +create-transit@|机械动力:跨境转运 (Create: Transit) +@-titanium-|钛 (Titanium) + + +power-gems-mod +sortscroller@sort-scroller +gt-exoskeleton-legs + +halfmasa@ +recasting2|重铸2 (Recasting 2) +wings-2|翅膀 2* +@dabaojianspracticaltools|大宝剑的实用工具(dabaojian's practical tools) +confluence-lootr|汇流来世Lootr兼容* +poopsky-flyimpact|空中厕所:蝇趴 (PoopSky: Flyimpact) + + +tfc-debark@ +nbs-player +create-routes@|机械动力:路線 (Create: Routes) + + + +ender-chest-persistence@ +adventure-item-names@adventureitemnames +adventure-item-stats@adventure-items-stats +genesis-an-origins-addon +create-lift-n-load@ +create-overpressure@createoverpressure|机械动力:气动物流 (Create: OverPressure) +big-tires@ +create-rolling-tones@ + +emf-compat-immersive-melodies@|实体模型特性兼容:沉浸式奏乐 (EMF Compat: Immersive Melodies) +emf-compat-not-enough-animations@not-enough-animations-emf-compat|实体模型特性兼容:更多动画 (EMF Compat: Not Enough Animations) +emf-compat-irons-spells-n-spellbooks@|实体模型特性兼容:Iron的法术与魔法书 (EMF Compat: Iron's Spells 'n Spellbooks) +emf-compat-supplementaries@|实体模型特性兼容:锦致装饰 (EMF Compat: Supplementaries) +aeronautics-modernui-compat@|航空学:现代化UI兼容 (Aeronautics: ModernUI Compat) + +emf-compat-carry-on@|实体模型特性兼容:搬运 (EMF Compat: Carry On) +emf-compat-quark@|实体模型特性兼容:夸克 (EMF Compat: Quark) +create-cosmonautics-expansion@ +create-aeronautics-discovery@ +emf-compat-exposure@U71mw0ra|实体模型特性兼容:拾光定影 (EMF Compat: Exposure) +emf-compat-watut@|实体模型特性兼容:WATUT (EMF Compat: WATUT) +emf-compat-tacz@|实体模型特性兼容:永恒枪械工坊零 (EMF Compat: TACZ) +create-parallel-tracks +create-liberate@|机械动力:解放斗士 (Create: Liberate) +create-heat-pipe|机械动力:导热管 (Create: Heat Pipe) +createbettercoal@createbetter-coal|机械动力:更好的煤炭 (Create: Better Coal) +create-schematicannon-auto-cutting|机械动力:蓝图炮自动切石 (Create: Schematicannon Auto Cutting) +functional-storage-js +create-railway-automation|机械动力:铁道智行 (Create: Railway Automation) + +create-steam-ages|机械动力:"蒸汽"时代 (Create: "Steam" Ages) +create-big-cannon-autogun-revolution@create-big-cannon-autogun-re|机械动力火炮:机炮革命 + +rollingcats-transit-expansion@|滚猫交通扩展* + + + +touhou-little-maid-senju|车万女仆:千手女仆 (Touhou Little maid:senju) +piggyback-legacy + + +macaws-betters@ +@dimension-locker|维度锁 (Dimension Locker) +@PG1zrN9b +the-joy-of-tinkering@ +world-alive@ + +twilight-boss-respawn|暮色Boss重生* +alphaprotocol@alpha-protocol +@vertical-half-brick|竖半砖! (KC Vertical Half Brick) +colored-redstone-reborn@ +thaumic-energistics-interaction|神秘能源交互* +better-removal +netmusic-kugou@netmusic_kugou|网络音乐机:酷狗概念版 (NetMusicNeedKuGou) +perfect-plushies@ +epiphany-journal@ +@nti-modern-impression-lerian-nature|NTI-现代印象-灵耶自然 (NTI-Modern Impression-lerian nature) +realistic-world-gen-revived|真实的世界生成:重生 (Realistic World Gen: Revived) + +railcraft-reborn@ + + +web-slinger +wandscape|魔法小镇* +goety-owned-null-guard-fix|诡厄巫法召唤物崩溃补丁* +pattern-checker|样板检测工具* +slashexpansion + +decorblock +starfall-core|沉星行旅 (StarFall: Core) +mekanism-me-integration +hextech-augments|海克斯强化* +general-tools@|通用工具* +dungeons-content-addon@dungeons-content-plus +natrium-mob-naming-utilities|Natrium生物命名实用* + +actinium-1-12-2-optimization +kamitesque +vibrancy@ +purification-totem|净化图腾* +@ae2-vm|AE2 合成虚拟机 (AE2 VM) + +gui-commands +ae2-texture-updater +maid-restaurant-storage@maid-restaruant-storage|女仆餐厅:储存 (Maid Restaurant: Storage) +reneighborly@ +astral-dice@|星之骰戏* +@fungal-infection-cataclysm|真菌感染:灾变 (Fungal Infection: Cataclysm) +kaleidoscope-immersive-eating|森罗物语: 沉浸式品尝 (Kaleidoscope: Immersive Eating)¨@immersive-eating|森罗物语: 沉浸式品尝 (Kaleidoscope: Immersive Eating) - 正在审核 +mcr-blocks-fix +@better-wither +crazyae-nyonification|疯狂的AE桥接器 (CrazyAE Nyonification) + + +custom-apotheosis-bosses|自定义神化BOSS (Icy Apotheosis Boss) +@particledrawing|粒子绘制器 (ParticleDrawing) +zconfig@ +souls-combat-hud@ +revo-ui|焕新UI (Revo UI) +@big-shot-lib +buff-particle-remover|第一人称药水粒子效果移除* +@worldreset-fabric +jade-tetra +@fmbautofishing|FMB 自动钓鱼 (FMB Auto Fishing) + +kubejs-assets-override|KubeJS 客户端资源覆盖 (KubeJS Assets Override) +modern-morph@ +mc-modsiance-netherite-monstrosity@alliance-netherite-monstrosity +fungal-infection-spore-ai|真菌感染:孢子 AI接入 (Spore AI) + +mekanism-magic|通用魔法* +@better-pie-chart +@custom-client-brand +@old-name-tags +@no-chat-clear +@map-rendering-disabler +@resource-pack-profiler +@no-see-through-text-displays +kings-monsters + +mmce-more-bus@|MMCE:更多总线 (MMCE More Bus) +simple-structure-desert +goety-invasion|诡厄侵扰 (Goety & Invasion) +respawn-recap + +iron-tanks-community-edition@irontanks-community-edition|更多储罐社区版* +buildcraft-community-edition-localizations@|建筑社区版:本地化 (BuildCraft Community Edition: Localizations) +@mob-rate|生物倍率 (Mob Rate) +souls-like-bosses@ +deadly-guardians +exp-ores-kots|经验矿石-耕云钓月版 (Exp Ores KOTS) +third-person-death-effect@ +player-hurt-number|玩家受伤数字显示* +conflux-map +polymorph-pages|多态合成翻页支持* +laser-eyes@ +symbiote-a-bonding-experience@ +@tp-players +beyond-craftlines|超越维度:合成链* +@water-in-nether + +holy-crusade|神圣征伐* +ultimateminepiece +maid-restaurant-now-open|女仆餐厅:营业中 (Maid Restaurant: Now Open)¨@maid-restaurant-business|女仆餐厅:营业中 (Maid Restaurant: Now Open) - 在审核 +range-harvest@|快速丰收* +@afk-camera +@anvilcraft-fe-grid|铁砧工艺:FE电网 (AnvilCraft FE Grid) +confluence-infernum@ +dlss-style +modern-baby-animals|现代宝宝* +darys-decoratives-curtains +totem-expansion +ae2-emi-shift-craft +initial-dim-choice|初始维度选单* + + + + +fresh-armor-bar@ +@strinova-hud|卡拉彼丘HUD (Strinova (CalabiYau) HUD) +cgm-ununofficial|MrCrayfish的枪:非非官方版 (MrCrayfish's Gun Mod: UnUnofficial) +irons-spells-light-pollution@|铁魔法:光污染 (Iron's Spells: Light Pollution) +twilightteleport@ +scp-610-flesh-that-hates@scp-610,-flesh-that-hates +adventurebags|冒险背包 (AdventureBags) +greedbundle|贪婪之袋 (GreedBundle) +big-xp-drops +talisman-jackiechan@|符咒* +calendar|日历 (Calendar Mod) +et-futurum-requiem-reforged|把未来带回现实:重铸版* + +ender-terg + +farmers-delight-meal-mastery@ +nfu-library +text-display-pen|自定义文本展示实体 (Text Display Editor) +see-through-my-mount + +@progression-reshuffled + + +embers-re-ignited@|余烬再复刻版 (Embers Re-Ignited) +@client-recipe-fix|客户端配方修复 (Client Recipe Fix) + +rustic-pipes +cosmiclib +touhou-shrine-maiden@|幻想巫女祭* + + +reflyky-addon-aot + + + +@dismantle-table|拆解台 (Dismantle Table) +momentum-limit|除其速势* + +@sprintable + +@jsonwrangler + + +mekanical-create@|通用动力* + +bren-reforged-unofficial@ +more-lectern-variants-unofficial|更多讲台非官方移植* +exp-ores@|经验矿石* +opencomputers-ii-modern +echostruct|残墟遗响* + +sarkazs-furnace-side-fables|无终奇语 (Sarkaz's Furnace Side Fables) +ink-scale|墨鳞* +mob-horde|游荡集群* + + +spice-of-overhaul@ +radium-re-reforged +show-dimension-in-name +jei-malum|JEI 灵灾 (JEI Malum) +spider-overhaul@ + + +asian-wildlife +south-american-wildlife +north-american-wildlife + + + +aeronautics-gravity-visualization|航空学:重力可视化 (Aeronautics:Gravity Visualization) +cannibalvillages@cannibal-villages + +interactivestuff@ +lib-5555ff +narwhal-mod +digital-storage-cloud|汤姆的简易存储:云上加仓* + +building-gadgets2-ae2-addon|建筑小帮手AE2插件 (Building Gadgets2 AE2 Addon) +sporeai|孢子AI (SporeAI) +fis-cordyceps|真菌感染:孢子 虫草 (FIS Cordyceps) +nenus-pop-plushies + +thermal-more-integral-components@ +recipehandlerragecraftedition|合成冲突消除:重铸版 (No More Recipe Conflict Reforged) +keymap-viewer + + + +multitools-enhanced@multitool-enhanced + +cc-my-peripheral-extender@|外设扩展器 (Peripheral Extender) +hbmtweaks + +pinyin-search-for-sophisticated-backpacks|精妙背包拼音搜索 (Sophisticated Backpacks Pinyin) +relog-health-fix|退出重进血量修复* +snow-imprints@ +more-difficult-wither-storm|更难的凋灵风暴* +jcraft-additions@ + +farmers-delight-26-neo-ver|农夫乐事 26 Neo 版 (Farmer's Delight 26 Neo Ver) +flans-mod-ultimate-2 +calamitymeter|灾厄怒气与肾上腺素 (Calamity Meter) + + + + +mixauth + +just-enough-drops|想查就查* +stopcheat +@item-editor +eden-ring-reforked@ +tusk-jcraft-addon@tusk-act-4-(jcraft-addon) +spin-jcraft-addon + + +clientcommands-neoforged@ +tetra-material-reclamation|Tetra材料返还* +@twilight-mobs-loots|暮色生物战利品 (Twilight Mobs Loots) +mekanism-more-multiblock + + +mythicbotany-legacy|神话植物学:遗产 (MythicBotany: Legacy) +torchautocraft|火把自动合成 (TorchAutoCraft) +custom-gun-continued@|自定义枪械永续* +cgc-config-addon@|自定义枪械永续配置扩展 (CGC Config Addon) +@fluorescentink|荧光墨水 (Fluorescent Ink) + + +ascendanttreasures +collection-book@|收藏册/收藏之书* +goety-astrology|诡厄巫法:星象学 (Goety: Astrology) +better-portals-refitted +jinzai-street-props|津仔的街道设施 (JINZAI Street Props) +@gtmadvancedhatch-novafork +@sparse-vertical-expansion|稀疏垂直扩展 (Sparse Vertical Expansion) +cubic-reworked +multicore-helper-coffee-expansion + +weeping-angels|哭泣天使* + + + + + + +applied-energistics-quantization@|应用能源:量子化 (Applied Energistics: Quantization) + +vmp-veinminerplus@veinminer-pro|连锁挖矿加强版 (VeinMinerPlus) +lootr-multiplayer-only|仅多人战利品* +vitrail-shaders@ +emc-motor|EMC 马达 (EMC Motor)¨@emcmotor|EMC 马达 (EMC Motor) - 在审核 +fangs-n-claws@ +chunkumulator@ + + + +enchantment-configurator@enchantment-config + + +graveless@ + +unofficial-tacz-renovated|永恒枪械工坊:零:焕新 (TaCZ: Renovated) +howling-werewolf|月厄狼人* +neosync@|新克隆 (NeoSync) +midnight-nightmares@ + + +@tab-compass +magnetization +lucis@ +azotosaurus-freesky-dragon-survival-add-on|氮沧龙 (Azotosaurus Freesky Dragon) +tml-avaritia-addon@tml_avaritia_addon|车万女仆:无尽贪婪 (TML Avaritia Addon) + + + +useful-projecte|更好的转化桌 (Useful ProjectE) + +macaws-for-terrafirmacraft-reborn +reportality@ +touhoulittlemaid-shogi|车万女仆:将棋附属 (TouhouLittleMaid Shogi) +desert-expansion + +es-entomology-spell@esentomology_spell|铁魔法:昆虫学 (Entomology Spell) + +@chinese-anti-japanese-war-equipment + + +create-sifting-create-fabric-6-0-8-1@create-sifting-fabric-updated|机械动力:筛子 - Create Fabric 6.0.8.1 (Create Sifting — Create Fabric 6.0.8.1) +@cursed-breeding + +@judgement-bird|审判鸟 (Judgement Bird) + + +@rick-technology|瑞克科技 (Rick Technology) + + +usefull-cows-much-more-cows-mod + +a-better-fishing@better-fishing-system +enchanted-fishing-line@ +no-tree-punching-no-additional-features|无树可撸(无额外功能版) (No Tree Punching (No Additional Features)) +mobends-x-cnpc-compatibility-layer +mango-fruitmc|芒果 (Mango) +homestead-sell-veggies|多多卖菜* +animal-husbandry@animalhusbandry + +kaleidoscopechinesefood-refabricated|森罗物语:国味——重织 (Kaleidoscope ChineseFood Refabricated)¨@kaleidoscopechinesefood-refabricated|森罗物语:国味——重织 (Kaleidoscope ChineseFood Refabricated) - 无法访问,可能为 + +@opticores +ether-craft|以太工艺* +jinghui2|镜回2 (JingHui 2) +rpgdamageoverhaul@rpgdo +dreamdisplays +matter-orb +fps-overlay@fpsoverlay|信息叠加层 (Info Overlay) +onegunlifetime@|一人一枪一辈子 (One Gun Lifetime) + +applied-exchange + + + + +tagtooltips@tag-tooltips|标签提示框 (Tag Tooltips) +mo-creatures-renewed +perfect-birbs@ + +karma-in-undertale@|罪业 (Karma) +mathmaster@|数学大师 +turnkey-factory + +alexs-caves-renewed + + +@twilight-ruined-portal|暮色废弃传送门 (Twilight Ruined Portal) + + +simplesulfur@ +visible-energy + +@mpkmod-2 +cobblemon-emi + +touhou-little-maid-carry-on|车万女仆:酒狐举高高 (Touhou Little Maid: Carry On) +tetra-tool-changer|Tetra工具等级编辑器 (Tetra tool changer) +takeitout +create-compactmachinespor +spell-forge-for-electroblobs-wizardry-redux +universal-control-overlay|通用扳手控制* +tfmg-community-edition@|机械动力:工业长路 社区版 (Create: TFMG Community Edition) +create-xaeros-map + +create-balloon@|机械动力:气球* +create-colored-connections@|机械动力:彩色连接线 (Create: Colored Connections) +explorers-compass-enhance +enhance-now|强化!就现在! (Enhance now!) +network-tool-plus@ +not-enough-dataenergitic-and-ae2lighteningtech|Not Enough DataEnergitic and AE2LighteningTech Incompatible Fix - 核心¨not-enough-dataenergitic-and-ae2lighteningtech-service|Not Enough DataEnergitic and AE2LighteningTech Incompatible Fix - 前置 +@create-gl4es-stencil-fix +gmcs-useful-gadgets|粥粥的实用物品 (GMC's Useful Gadgets) +modpedia +maid-file-manager|车万女仆档案(迁移)管理器* + +littlemaidsomeinstructions@|车万女仆:一些指令 (Little Maid: Some Instructions) +backpack-side-gui-kots|精妙背包扩展GUI - 耕云钓月版 (Backpack Side GUI KOTS) +slashblade-return-journey|拔刀剑:归途 (SlashBlade: Return Journey) +export-language-keys-for-compasses|为指南针导出语言键 (Export Language Keys for Compasses) +quizcraft-pro + +cnpc-obj-models-compat +cnpc-immersiveboss +@firecracker|鞭炮 (Firecrackers) +limitless-logistics +create-better-belt@|机械动力:更好的传送带 (Create: Better Belt) +strife-and-calamity@|战伐祸乱 (Strife and Calamity) +forgiving-item|仁慈的物品* +me-building-gadgets@gadgetsmenetwork +redstone-flight-control-set@|红石飞控组件* +voxkbd@|沃克键盘 (VoxKbd) +alexs-mobs-delight-neoforge|Alex 的生物乐事 NeoForge 版 (Alex's Mobs Delight NF) +craftflow@craftfolw|合成流 (CraftFlow) +the-impossible-library-new + +ice-and-fire-dragon-seeking-staff|冰火传说:寻龙杖 (Ice and Fire Dragon Seeking Staff) + + +terra-plate-extension + +xiaozhitpa@|小志的TPA (Xiaozhi TPA) +@all-is-random|一切皆随机 (All Is Random) + +ancient-tech|远古科技* +@third-person-nametags|第三人称玩家名称 (Third Person Nametags) +@daysplayed|游戏天数 (Days Played) +true-adaptive-music@ +fused-critical|统合暴击* +nimble-pattern|灵巧样板* +let-s-do-bugs-fix +@enigmatic_extra|神秘遗物:附加 (Enigmatic Extra) +music-triggers-new +scp-facility@ +advanced-core-info@ +playerblockstate|玩家区块状态 +vlorant|无畏契约 (Valorant) +immortalitytotem|永生图腾 (Immortality Totem) +@extractinatorforked|提炼机(分支版) (ExtractinatorForked) +simple-rpg +portable-clicker|随身连点器* +mc-paint-rebirth|涂画 (MC Paint:Rebirth) +artifacts-delight +subnautica-ember +frost-dragon|寒霜龙* + +@me-spawner|ME刷怪笼 (ME Spawner) +eeeabs-mobs-lost-content-addon +epca-pestilence-of-the-marrow|归寄-疫至深髓 (EPCA-Pestilence of the Marrow) +apocalypse-tnt +metal-upscaler +draw-the-world|手绘世界* + +hdd-mode|HDD模式 (HDD Mode) + +structure-no-overlap|阻止结构重叠* +@planifolia +tacz-dual-wield +mana-transform|魔力转换* + +lootr-break-to-loot|Lootr:破箱取宝 (Lootr: Break to Loot) +@the-aurorian-reborn|极光幽境重生 (The Aurorian Reborn) +better-end-sky-retro|更好的末地天空:复古* +emi-structures@ +mtr-psd-lcd +auto-tosser|自动投物器* +backport-update +legendary-no-more|传奇不再* + + + +kaleidoscope-bountiful-fares@kaleidoscope-bountifulfares|森罗物语:丰饶食记* + +roxy@ +unseen-world@ +botany-pots-renewed +ifc-integrations@ +bleed-zone-7|七号区 (Bleed Zone) +first-class-flying@|头等飞行* +one-dimension@ + + +thunderhead +new-armor-system@|新护甲系统* +codxlib@ +astages-ftb-quests@ +mob-spawner-tweaks|刷怪笼控制与机制修改 (Spawner Tweaks) +animalia-exotica@animalia-bestiary + +visual-iron-mobs-edit@vimevisual-irons-mobs-edit|全民普法/可视化铁魔法生物编辑器 (Visual Iron's Mobs Edit) +the-twilight-moonshine|暮色月光* +anka-mobs|安卡顽敌* +walk-reveal +dbes-spark-engine|星火引擎 (DBE's Spark Engine) +@simple-text-chat|简单的文本聊天 (Simple Text Chat) +tinkers-part-reclaimer|匠魂部件返还* + +the-corners-renewed +@animated-typing +dawn-star@|黎明之星* +@kubejs-studio +rock-n-stone@ +chat-highlight-sound@chat-highlight-n-sound +special-effect-text@|特效文本* + +focus-value|专注值* + +glass-bridge +map-3d-usseewa + +beecrasy|蜂会* +skillplants|技能:位面 (Skill Plants) +mythical-extension +memoryjournal|旅行手账 (MemoryJournal) +realistic-ore-veins +ncb|新世纪道路方块 (New Century Road Block) + +immersive-portal +immersive-portal-irons-spells-n-spellbooks-addon|沉浸式传送门法术 (Immersive Portal - Iron's Spells 'n Spellbooks Addon) +mekanism-expand|通用机械:拓展 (Mekanism:Expand) + +@mtr-fare-pro +lingxiis-mtr3-backport-pack|泠惜的MTR拓展 (Lingxi's MTR Transport Extension) +@pingnametags +@ponder-plus-plus +rockore|开山掘石 (RockOre) +trade-picker@ + +benchmark|性能基准测试* +rings-of-ascension-reborn|提升戒指:重生 (Rings of Ascension:Reborn) + +real-time-craftingtable|实时工作台 (Real-time CraftingTable) +cnpc-compatible-with-kingdomcome-combat|CNPC兼容:天国的战斗 (CNPC compatible with Kingdomcome Combat) +quick-craft +tetrawear +etg-crystal-engineering|ETG晶体工程 (ETG Crystal Engineering) + +tinkers-construct-filter|匠魂材料筛选* +faketimemod|客户端时间 (FakeTimeMod) +energy-tower|能量塔科技* +hec-veinmining|『HEC』恒创-功能全面的连锁采集 (HEC VeinMining) + +enigmatic-liberator|神秘遗物:解放者* +survivors-arsenal + +devil-mine-craft + +shaderpack-kaiguan|光影控制器 (Shaderpack KaiGuan) +block-drops-tweaker +deathlogplus@ +attribute-modify@ +frutigeraero-craft@frutigeraero_craft + + +armadillo|犰狳 1.12.2* +yujian-craft|御剑工艺* + + +@create-patina|机械动力:铜染光阴 (Create: Patina) +mite-underworld|MITE:地下世界 (MITE:Underworld) +@better-sign|更好的告示牌 (Better Sign) + + +eye-cross@|眼之交叉* +mikus-leek-ryouri@|初音的葱料理 (Miku's Leek Ryouri) +transparenthud|可透明的用户界面 (Transparent Hud) + +frost-ship +ultra-sprite-kaiju-battleground|奥特幻影大怪兽格斗* +youkaishomecoming-curios-transfer-the-fix|妖怪们的归家饰品跨维度修复 (Youkaishomecoming Curios Transfer the Fix) +particle-shield|粒子屏蔽* + +frutigeraerodecor@ +@dimension-level-data +haven-cobble-generators +applied-generators@ +cataclysm-x-yungs + +stellarview-redshift@ +tech-ascension-stellar-age@techascension|科技飞升 (Tech Ascension) + +@lambda +destruction-infection|毁灭感染* + +cards-legends-of-the-three-kingdoms-basic|卡牌:三国杀(基本牌) (Cards: Legends of the Three Kingdoms(Basic)) +cards-legends-of-the-three-kingdoms-character|卡牌:三国杀(武将牌) (Cards:Legends of the Three Kingdoms(Character)) +cards-legends-of-the-three-kingdoms-identity|卡牌:三国杀(身份牌) (Cards: Legends of the Three Kingdoms(Identity)) +hg-keyradial|冲突按键二级菜单 (HG KeyRadial) + +originfall-cataclysm|源石计划* + +harvest-festival-replanted|丰收物语:非官方版 (Harvest Festival-Unofficial) +@simudelight|模拟大都市:农夫 (SimuDelight) + + +modern-overclock-for-gtm|现代化超频 (Modern Overclock For GTM) +sable-stop-now|哦不快停下 (Sable: Stop Now) +create-firefighting-additions@|机械动力:消防附加 (Create: FireFighting Additions) +@helium_client +@i18nautoupdatemod +simpledusts +@usage-ticker-reloaded|物品计数器重制版 (Usage Ticker Reloaded) +kubejs-eyejs +fated-inventory@ +bottled-milk + + +slag-n-embers@ +real-time-enchantmenttable|实时附魔台 (Real-time EnchantmentTable) + + +@blockdiff|区域快照 (BlockDiff) + +touhou-little-maid-personality|车万女仆:性格 (Touhou Little Maid - Personality) +little-maid-more-action|车万女仆附属:更多动作* +tlm-speak-human-language|车万女仆:说人话 + + +arsarms-player-subject|ArsArms:玩家主体 (ArsArms: Player Subject) +industrial-crops|尖端工业化农作物* +grounded-electroblobs-wizardry-redux +prefab-goety-ritual-altars|预制建筑 - 诡厄巫法祭坛 (Prefab - Goety Ritual Altars) +skeleton-spell|可施法骷髅 (Skeleton With Spell) +l-ender-s-cataclysm-x-t-o-magic-n-extras + + +@serendimodguarder +emerald-edition@ +dabaojians-dummy|大宝剑的假人 (Dabaojian's Dummy) +@endefinder|末影探索者 (Endefinder) +@sinkhole-restorer + +@death_back +gregtech-calculator-board +@equipment-inscriber|装备刻录仪 (Equipment Inscriber) +cave-roamer|洞穴漫游者* +valemetry|值域协议* +neofuture +velvet-advanced-alloys +neko-adventure|猫的旅途* +better-gold-expansion|更有用的金 新约* + + +world-painter +rphone +cave-game +generic-henshin|通用变身* +biolith-x-mcreator-biome-fix +mcphone +curios-pre-calculate-available-slots +wikievery|万象通揽 (WikiEvery)¨@wikievery|万象通揽 (WikiEvery) - 正在审核 + +kamen-rider-zeztz +viafabricplus-bedrock@ + +@muisc-disc|音乐唱片 (Music Disc) +torcherinoae|AE加速器 (Torcherino AE) + +tinkers-jei-prefilter +@record-able +shovel-something-offhand@shovel_somethingoffhand|铲物,双手合一 (Shovel Something/Offhand) +target-dummy-dependency-fix|试验假人前置修复* +diamond-in-the-rough +sfm-factory-studio|SFM 智造工坊 (SFM Factory Studio) + +@qiusoptimization|Qiu的优化 (Qiu's Optimization) +bc-lightning-terrain|可视化地形节点编辑器 (LightningTerrain) +advanced-worldgen-info +spellcaster + + +@cosmonautics-deep-air +ts-modify +block-outline-customizer@ +@ironsspellbooks-ui|铁魔法UI (ISS UI) +curios-swich|饰品套装切换 (Curios Set Switcher) +slashblade-reborn|SEAC拔刀剑附属 旧梦重铸 (slashblade legacy compat) + +apex-arcana-irons-spells-n-spellbooks-addon +@flaming_remastered|火焰熊熊:重制版 (Flaming: REmastered) +bigger-stacks-2|更大堆叠2* + +irons-spell-smithing-template-addon|铁魔法:锻造模板附加 (Iron's Spell: Smithing Template Addon) +botania-biomes@biotania +codechickencore-1-8-9 +notenoughitems-1-8-9 + + +mekanismceu-qio-storage|MekCeu QIO 存储 (MekanismCeu-QIO-Storage) + + +create-big-cannons-ballistic-computer|机械动力火炮:弹道计算机 (Create Big Cannons: Ballistic Computer) +mithril-banner-protection|秘银旗帜保护* +ancient-remnants +@fourleafcrystal|四叶水晶 (Four Leaf Crystal) +fog-fix-legacy@ +maid-musketeer + +mod-access-control@|模组准入控制* +terracliff@ + +damage-stats|伤害溯源* +slashblade-resharped-recipe-fix|拔刀剑:合成表错乱修复补丁 (SlashBlade Resharped Recipe Fix) +lei-lazy-enough-items|懒人物品管理器 (Lazy Enough Items) +@xehtascing +@hexsbm +hexxy-attributes@hexxyattributes +@hex-machina +ioticblocks@ +@phlexiful +@hexshield +@hexchanting +@hexdeco +@slate-works|石板工程 (Slate Works) +caduceus@ +hexthings@ +@yaha +hexxyplanes@ +@hexboard +hexweb@ +@hextext +@abitmorehex-recasted +@kvha +@hexportation +@heartxxy +@help-from-hexxy +@hexic +@iotaworks +hexcessible@|咒法通达* +@hexpats +@hexthingy +@merlingsgrace +hexstruction@ +hierophantics@ +complex-hex@ +@hex-casting-media-display +@lapisworks|青金工程 (Lapisworks) +@hex-spell-wheel +@lesser-teleport +@hexdentity +cinnamoroll-friends|玉桂狗之友* +mekanisms-modern-industrialization +hgs-backpack-smelter|打包烧制 (HG's Backpack Smelter) +hexjsneo@ + + +apoli-stream-leak + +rail-placement-fix@|铁轨放置修复* +scorchedgunsreforged-addon|Scorched-Guns-2-Flintlocks 备弹配置 (ScorchedGunsReforged-Addon) +gunsmiths-gadgets-n-gizmos +boids-reforged +joy-of-painting-reforged|绘画之乐重铸版 (Joy of Painting Reforged) +sink-faster +codechickenlib-1-8-9 +toastsweapons|吐司的武器 (Toast's Weapons) +scalable-lux-x-sable-compat +simplehats-lite|简单帽子:轻量版 (SimpleHats-Lite) +custom-mob-spells|丘丘人也要学魔法* +enigmatic-armor@|神秘护甲* +prolong|效果延长* + +better-fox-trot-brew|更好的狐步酿香* +auto-librarian + + +more-coal-craft|更多的煤炭工艺* + +universal-talent-editor|通用可视化天赋编辑器* + + +bdlib-rescaled +cys-attributes +jei-unofficial +@quadcasting +@pointless-machine +@minihex +hexssence@ +@hexshadowing +@hexvidia +@almightly-staff|万法之杖 (Almightly Staff) +@focalworks +@hexoncommand +atomchat@|原子聊天 (AtomChat) +scorched-guns-scg-extra@ + +bread-reborn-reborn@|面包重生:重生 (Bread Reborn: Reborn) +somnia-awoken-reforged|梦醒时分 - 重制 (Somnia-Awoken-Reforged) + +create-fly-steam-n-rails +nointernet|禁止联网 (NoInternet) + + +@pigmentum +bee-in-a-jar-reborn|罐中蜜蜂 — 重生 (Bee in a Jar - Reborn) +beyondinfinity|超越无限 (BeyondInfinity) +compressorfix|无尽奇点压缩机修复 (CompressorFix) +mahoraga +spellbooks-of-twilight-irons-spells-x-the-twilight +irons-spells-n-spellbooks-elden-ring|Iron的法术与魔法书:艾尔登法环 (Iron's Spells 'n Spellbooks: Elden Ring) +who-goes-there|谁在那! (Who Goes There?) +wine-cabinet-compat|森罗Vinery酒柜兼容 (wine-cabinet-compat) +auto-window-size@autowindowsize|自动窗口大小* +enchantlib-example +silent-gear-catalog|寂静装备目录* +@hexnbt +@hexintent +@hexpose +@stackpatterns +teekaspaste@ + +black-ops-armor-plate|黑色行动防弹插板 (Black ops Armor Plate) +simple-boss-damage-cap +murmol@murmol-mod|幻星之咒* + + +eai-goety-patch|阻止使徒能力外泄!/ Enhanced AI & Goety 补丁 (EAI & Goety Patch) + +do-another-barrel-roll@ +star-fantasy-library + + +cgc-compat-packloader@|自定义枪械永续兼容:包加载 (CGC Compat: PackLoader) +matesignal-unofficial +cgc-compat-machinemax@|自定义枪械永续兼容:极械工坊 (CGC Compat: MachineMax) +framed-maps-fix@ +onlytinkeralmost|几乎只有匠魂 (OnlyTinkerAlmost) +kineticcore +universal-preview|通用预览* +mekanism-heated|通用机械:热力学 (Mekanism: Heated) +cobblemon-player-xp-share|方块宝可梦:玩家经验共享 (Cobblemon Player XP Share) +mcphone-apps-extension + +@fresco +polymorph-legacy|多态合成-怀旧* + +spirit-pearl-and-demon-pill|灵珠与魔丸 (Spirit Pearl and Demon Pill) + +apollyoncat|亚小猫 (ApollyonCat) + + +communism-mod-reborn|共产主义重生* + +zbw3790s-fashion@3790s-vanilla-style-fashion|3790的时装 (3790's Fashion) + +ae2-stuff-rescaled + +maid-tech|女仆科技拓展* + +silk-road|丝绸之路* +grand-strategy|大战略 (GrandStrategyMC) +back-to-greece|重返希腊* +supporoform +dragon-mounts-reforged +dog-reborn@dogreborn|狼复活* +blood-reborn|血之重生* +place-skull-on-death +blow-balls +death-teleporter + +dglib¨@dglib|DGLib - 已停更 +mixintrace-reborn +defaulted-hotkeys + +integrated-core +shining-champions|闪耀!冠军生物* +modular-machinery-community-refoxed@|模块化机械:社区版非官方重制版 (Modular Machinery:Community Refoxed) +pyrologer-and-friends-port +curse-of-abyss +applied-oritech +fancy-tab-sections@fancytabsections +frameframe@ +tinkers-sort|工匠典序 (Tinkers' Sort) +kaleidoscope-flora|森罗物语:花香四溢* +maidassemblyline|女仆流水线 (MaidAssemblyLine) +tlm-statue-animation@|车万女仆:雕像动画 (TLM: Statue Animation) +better-disenchanter|更好的祛魔台* + +custom-rarity +tame-ervery-mob@tame-every-mob +apocalypselib + +terror-of-the-spinosaurus-dinosaur-horror +defense-innovation@defense-innovation-al1s|防御革新* +carry-on-creative-world|随身创境 (Carry-on Creative World) +warriorcats-events@warrior-cats-events +@stamina-dltb + + + +more-compressed-mek-factories +more-compressed-mek-generators +minecamera|我的视角 (MineCamera) + +create-magic-blueprint|机械动力:魔法蓝图 (Create: Magic Blueprint) +really-fast-load +bossesrise-cinematic-unlock|Bosses'Rise 镜头限制移除 (Bosses'Rise Cinematic Unlock) +dnfenhance@|装备强化 (DNF Enhance) +matcha-flavoured-fabric@ +mc-258859@ +bayou-reimagined|长沼蓝调:重新构想 (Bayou Blues: Reimagined) +compression-upgrade-kits +sky-island-generator|空岛生成预设* +wds-selling-bin@ +baochuan-treasure-ships|宝船 (Baochuan - Treasure Ships) +@lookatplayer +alexs-caves-continued@ +legendary-shrines + +fastchunkgen +pets-mod@ +perpetual-day-or-perpetual-night|永昼?永夜?* +@zephyr-hauler +masiks-puzzle-dungeon@puzzle-dungeon +i-cant-play-mite-|我不能玩MITE (I Can't Play MITE) + +more-infinite-arrows-enchantments|更多的无限附魔书* +alexs-mobs-continued@ +accessories-compat-vanilla@ +@fox-trot-brew-refabricated|狐步酿香:重织 (Fox Trot Brew (Refabricated)) +izanami|伊邪娜美* +most-important-question@|史生大事* +continuity-neoforged@ +@elysium + +compact-extreme-reactor|紧凑极限反应堆* +immersivethunder-reforged@ +lolaccessories|英雄联盟饰品 (LOLAccessories) +better-advanced-tooltips@ +chemek@|通用化学 (CheMek) +yes-slashblade|YSM × 拔刀剑重锋 兼容修复 (Yes Steve Model x SlashBlade Fix) +yes-bsl|BSL 光影 × YSM 兼容修复 (YES_BSL) + +enhanced-equipment-display +dynamic-surroundings-rebirth@ +darwin-soldier|达尔文士兵* +the-possessive-knocker + +extra-dungeons@ +desert-dungeon-dungeonz-addon@ +stackablepotionsplus|可堆叠的药水:强化 (Stackable Potions Plus) +tinkers-meat-shredder|匠魂绞肉锯* +mod-menu-forge-by-bogdan-tokarev|模组菜单(Forge) (Mod Menu (Forge)) +betterchunkculling +create-aeronautics-fade|机械动力航空学:虚化 (Create Aeronautics: Fade) +catch-the-rainbow|彩虹捕手* + +mekanism-combining-factory|融合工厂副输入升级 (MekAddon / Mekanism Combining Factory) + +cbr-create-blaze-burner-recipe|机械动力:烈焰人燃烧室配方 (Create: Blaze Burner Recipe) + +cape-3d-layers|3D披风层 (Cape 3D Layer) + +legendary-inventory + +guns-workshop-origins|枪械工坊:起源* + + +ae2-pattern-disk@|AE2样板磁盘 (AE2 Pattern Disk) + +user-9k2h8ismc9exbn45-eternal-food|永恒原版食物 (Eternal Food) +advanced-apples +flexible-dodge|灵巧闪避* +@misia-ui|米西娅UI (Misia UI) + +carry-on-toggle|搬运开关* + +blacksoil-delight|黑土乐事* +quarkful-delight +collectors-reap-unofficial-port@ + +larp-eccentric-tome + +goety-starfantasy|诡厄巫法:星翼幻想 (Goety: StarFantasy) + +custom-enigmatic-relic|自定义七咒之遗物* +emerging-technology-hydroponics-community-edition|新兴技术:社区版 (Emerging Technology: Community Edition) +mebahels-rpg-villager-quests-and-companions@ +damagestats@|伤害统计 (DamageStats) + + +sdm-shop-2 +villagerstradingplus@ +relic-ward|遗境守望* +mc-phone|MC手机 (MCPhone) +applied-slash|Slash存储元件* + + +trading-stalls@ +village-shop-system@ +modern-content-backport|现代内容移植* +eye-of-dragons-reborn|龙之眼:重生 (Eye of Dragons Reborn) +relics-thirteen-flames +jaams-weaponry@ + + +texture-scaler|自动贴图缩放Mod* + +throwabletorches@ +hooked-sable-physics-link|抓钩物理结构兼容* +cataclysm-curios-edited|灾变饰品槽位修改* +@primogemcraft|原石工艺:重生 (PrimogemCraftNeo) +ice-and-fire-rlcraft-modern|冰火传说-RLCraft现代版 (Ice and Fire: RLCraft Modern) +miguelnetwork|米格尔网络 (MiguelNetwork) +schematicaplusreforged +ragecraftmalisisdoors + +maldict@|咒邪* + +classic-gamerule@ +poopy-cobblemon +quick-container-tabs@|便捷容器标签* +mineagent@ +@crosshair-damage-indicator +bedrock-allow-deny-border +@villagers-wand +webshooter +applied-ember|应用余烬* +upgrader-items@upgrade-items + + +@createmaxcraft + + + +durability-display-hud@durability-display-hud-hud|耐久显示 HUD (Durability Display HUD) +ultimate-difficulty +cnpc-x-spiritoffight-compatibility +cinematic-zoom@cinematiczoom-original + +mechanical-infection|械器感染* +tinkers-of-cataclysm|灾变匠魂材料 (Tinkers of Cataclysm) +rainbow-lotus|彩虹莲* +twilight-sparks-delight|暮光乐事 / 暮光农家乐 (Twilight Spark's Delight) +cataclysm-primed-soul-addon-unofficial-work-in + +blackannins-drone@|BlackAnnin的无人机 (BlackAnnin's Drone) +better-ie-coke-oven@|更好的沉浸工程焦炉 (Better IE Coke Oven) +eosp2p +more-health-reforged|更多生命:重铸版 (More Health (Reforged)) +fastadvancements +vulkan-early-loading@|Vulkan 早期加载* +@nera_jadis +refugee|难民* +genshin-moonoracle|原神:月谕圣牌 (Genshin Moon Oracle) +the-call-of-carmen|卡门的呼唤 (The Call of Carmen) +manabove|高人一等 (ManAbove) +create-shining-stage|机械动力:闪耀舞台* +external-override-model-library@|外置模型拓展* +simpledifficulty-reforged|坚定意志:重铸版 (SimpleDifficulty Reforged) + +continuity-version-fix +mekanism-empowered-unleashed|通用机械:增强·解放 (Mekanism: Empowered Unleashed) +conveyor-belt-plus|传送带+* +industrial-foregoing-extra|工业先锋扩展* +sanity-renewed-for-tfc@|群峦San值 (Sanity: Renewed For TFC) +advanced-tfc-info@|高级群峦信息显示 (Advanced TFC Info) +simple-tfc-ore-sluicing@|简单群峦洗矿 (Simple TFC Ore Sluicing) +mystian-apiary@ + +easy-villagers-iron-spells-compat|铁魔法简单村民兼容* + +mezzconfiggui@ +mezzconfig@ +snow-waifu-spell-resumed|雪下的誓言·再续 (Snow Waifu Spell·Resumed) +modernui-unofficial-port|现代化UI非官方版 (ModernUI Unofficial Port) +storage-consolidator|TOM存储整理* + +libtooltips@ +@stardewvalley|Stardewvalley星露谷 (Stardewvalley) +mds-api@ +golden-tweaks@goldentweaks|金子修改* +spartan-shields-fabric@|斯巴达之盾 [非官方版] (Spartan Shields [Unofficial]) +fontswap@ +sync-fix +integrated-mowzies-mobs@ +small-plane|小飞机 (SmallPlane) +timeless-echoes +cargo-ghast|货运善魂* +fancy-notify@ +overshot|蓄力刚射* +command-adjust|指令调整* +@usedtotemmessage +totemdoll@|图腾娃娃 (TotemDoll) +icy-l2hostility-small-extra|莱特兰恶意小拓展 (Icy L2Hostility Small Extra) + + +kubejs-pneumaticcraft +eye-of-dragons-reborn-additions|龙之眼:重生 - 额外内容 (Eye of Dragons Reborn: Additions) +river-fishing@ +mineral-xp-siphon|矿物经验汲取 (Mineral XP Siphon) +teleport-gate@teleportgate|传送方块* +performancehud@ +chunksmith-modern-gui|Chunksmith现代化GUI (Chunksmith Modern GUI) + + +enhanced-iss-ui|铁魔法法术栏增强 (Enhanced ISS UI) +hassium@hassium-limuqy|小水管救星* +sealed-word-orb@|缄言珠* +travelers-backpack-stack|旅行者背包:堆叠提升 (Traveler's Backpack Stack) + +pickupcard|拾起卡片 (Pick Up Card) +tinker-foundry|匠魂冶炼* +akadaemon-addon +eternal-golden-apple|永恒金苹果* +modern-damage-system@|现代化伤害系统* + + +@elementmark|元素标记 (ElementMark) + +elytra-renewed@elytra_renewed|鞘翅革新* +parallelapi +field-emitters@ +legendary-block-entities@ +data-sync-lib +blade-forge-workshop|锻刀工坊* +villager-backport + +item-drops@ +corpseorigin|尸源 (CorpseOrigin) +ommc-fg + +player-interact@|玩家交互* +minecraft-earth-reborn + + + + + + + + + +laicaps@ +epiphany-extra@|顿悟 · 拓展* +eternity-enchantment|永恒附魔* +@ae2cc-bridge-forge + +lycanitestweaks + +unchanged-world +bricklands + +applied-delight@|应用乐事* +better-snow-villages|更好的雪村* +@cyberware-unofficial|机械改造非官方版 (Cyberware Unofficial) +antos + + +@ae2-channel-alert|AE2频道过载提示音 (AE2 Channel Alert) +ssc-lso-compat +locknar-door-kick|TaCZ-动画附加 (Tacz-Animation-Additions) +loot-bag-renewed-fixed-version|战利品包-修复版 (Loot Bag - Renewed (Fixed Version)) +kmd-travel@ +echoing-magic-esoteric-spells-of-the-fallen +ultra-cell|究极元件 (UltraCell) +dragon-loot-reforge|龙战利品:重铸 (Dragon Loot ReForge) +the-13th-blade|十三契刃 (Thirteen Blade) + +quality-tools-jei-compat|工具品质 JEI 兼容 (Quality Tools JEI Compat) +iron-jetpacks-port +world-music-player|世界音乐播放器* + +@vcoasters +smidgeon-o-bliss-legacy@ + +@woodlandsextra +guardian-stone@|玩家传送石头* + +smartmaid|智能女仆 (SmartMaid) +furkin@|绒亲* +greedy-villagers@|更良心的村民* +cataclysm-magic-compat|灾变魔法兼容* +pikafishing|皮卡钓鱼 (PikaFishing) +teyvat-artifacts|提瓦特饰品* +exploitation-timer@ +modern-regen-mod@modern-regen +dappled-camp|斑驳营地* +auto-mod-fetcher@automodfetcher +ore-food@|矿物食品/矿物牛排* +@radial +create-feed-me-packages|机械动力:喂我发包! (Create: Feed Me Packages!) +attributelimiter@attribute-limiter|属性限制器 (AttributeLimiter) +easy-ants +cgc-compat-legacyitem@|自定义枪械永续兼容:夕拾 (CGC Compat: LegacyItem) +cgc-compat-shader@|自定义枪械永续兼容:光影 (CGC Compat: Shader) +@cc-audio-expand|CC音频拓展 (CC Audio Expand) +jdt-kubejs +ans-ars-nouveau-spellbooks-irons-spells-addon|魔艺法术 (Ars Nouveau Spellbooks - Iron's Spells Addon) +sophisticated-plus|精妙背包++* + +dimensiontech@dimension-tech|维度科技 (DimensionTech) +eunomia +gregtech-true-age-of-steam@ +chest-cavity-legacy|胸腔-复古版* +lootbag-patches-fixed|战利品袋-修复版 (LootBag-Patches-Fixed) +mxt|觅仙途 (MiXianTu) + +create-easy-stock-ticker@ +create-on-the-move +create-high-skies +create-stock-keeper-bookmarks@ +create-superflat-dimension|机械动力:超平坦维度 (Create: Superflat Dimension) +create-crystal-industry|晶簇工业 (Create: Crystal Industry) +attribute-reset|属性重置* + +@aoa-3.6.11-fix-addon|虚无世界3.6.11修复 (AoA 3.6.11 Fix Addon) + +quest-language-selection|FTB任务书语言选择* +ae2-lightning-tech-reborn|闪电科技:重生 (AE2 Lightning Tech Reborn) +@create-bionics +create-compact-flap@ +fov-utils + +caerula-arbor-crop-compatibility|深蓝之树作物兼容* + +barley-porridge +end-element-journey|终末-元素奇旅 (End-Element Journey) +tick-weave +do-a-barrel-roll-1-12-port +epic-fight-retaliation +avaritia-furnace@|无尽贪婪熔炉* +haibaras-traverse|海拔拉的测绘 (Haibara's Traverse) + + + +wgpu-mc-neo|琥珀金 (Neolectrum) +olympus-mythology|奥林匹斯! (Olympus!) +mts-iv-official-automobile-pack@immersive-vehicles-official-automobile-pack|沉浸车辆官方汽车包 (Immersive Vehicles - Official Automobile Pack) +@windows-priority-boost + +forbidden-chimera|禁忌嵌合体* +@theseus-quests +macaws-roads-and-streets +@gameinfo|GameInfo 非官方移植 (Gameinfo for 26.3) + + +vttas-viewtrackingtargetacquisitionsystem|视角追踪索敌系统 (View Tracking Target Acquisition System) +curvy-blocks|弯曲方块* +@watermedia-android-bridge +teyvats-delight-meeting-in-outrealm|提瓦特乐事:异世相逢 (Teyvat's Delight Meeting in Outrealm) + +better-beyond-dimensions|更好的超越维度* +venominus-bloodbath +godfall-trinkets|登神残饰* +on-ancient-ground-core@ +shape-shifter-curse-forge|幻形者诅咒非官方forge版* + +otcaddon + +end-parasitize-and-spartan-all|归寄万物与斯巴达全系列 (End-Parasitize and Spartan All) +@flashier-flashlights + +advanced-fake-player@ +nimble-spiders +penetrable-platform|穿透平台* +more-backpack-upgrades-neoforge@ +mmce-advancedbuildertool + + +@simple-backups-for-fabric|简单备份 Fabric 版 (Simple Backups for Fabric) + +autofarming|自动农场 (Auto Farm / AutoFarming) +not-enough-game-designs + +endless-inventory-refined-integration@|留了亿手 (Endless-Inventory Refined Integration) + +@guard-villagers-tacz-support|警卫村民 TaCZ 支持 (Guard Villagers TACZ Support) +smart-workbench|智能工作台* +variants-more +salt-powder|盐与火药 (Salt & Powder) +oritech-addons-one@|Oritech:插件合一* +junjietrafficmod|俊杰的交通模组 (JunjieTraffic) +how-to-go@howtogo|问道* + +epic-sanji-fork +@anima-mitama|赋灵 (Anima) +bloodbathen@ + +@p2p-logistics +@bedrock-skins +@tlm-substitute-jizo-expand|车万女仆:替身地藏扩展 (Touhou Little Maid: Substitute-Jizo-Expand) +maidmorework|酒狐的更多工作 (MaidMoreWork) + + + + +touhoulittlemaidcombat|车万女仆:战斗增强 (Touhou Little Maid: Combat Plus) +promaid|更智能的车万女仆* + + +@touhou-little-maidmaid-command-chat|车万女仆:女仆指令聊天 (Touhou Little Maid: Maid Command Chat) +touhou-little-maid-unofficial|车万女仆:非官方移植版 (Touhou Little Maid: Unofficial) +fenneclib|漠漠的前置库 (FennecLib / MomoLib) +amethyst-craft-time-and-space|紫水晶工艺 (Amethyst Craft) + +wither-sickness-potions|凋零腐败药水* + + +sable-aeronautics-scaling@|航空学物理结构缩放 (sable aeronautics scaling) +mob-forceload|远游但载* +creeper-spider-mod +nsukdelightcropaddons|NSUK:乐事作物附属 (NSUK Delight Crop Addons) + +tacz-burst-pitch|压音订正 (TACZ Burst Pitch) + +@bye-bye-buttons +@tpa-tinmoli +anvilcraft-tofusthinking|铁砧工艺:豆之思维 (AnvilCraft: Tofu's Thinking) +infinity-what|什么无限?* + +hexcasting-legacy|咒法学:遗产 (Hex Casting: Legacy) + +kubejs-custom-rarity + +loot-stacker|战利品自动堆叠* + + +gretech-5-unofficial-gtnh|格雷科技 5 - GTNH 非官方版 (Gretech 5 Unofficial GTNH) + + +directtouch + +nova-inventory +madness-combat-mod +render-360-core + +keerdms-binoculars@ +apple-trees-revived-reborn|苹果树:再复生 (Apple Trees Revived: Reborn) +sky-isle|天空岛* + +veinrebirth|矿脉重生 (VeinRebirth) +hedges-bestiary +better-inventory-and-backpacks +kaleidoscope-world-liquor-refabricated|森罗物语:世界名酒——重织 (Kaleidoscope WorldLiquor Refabricated) +refinedstorage-fabric|精致存储-Fabric (Refined Storage-Fabric) +merlinlib +tinkers-organ-new|匠器之锻 (Tinkers Organ) +flex-fov-cleanroom-port|扭曲视野(Cleanroom 移植版) (Flex FOV (Cleanroom Port)) +the-boys-unbalanced@|黑袍纠察队:失衡 (The Boys: Unbalanced) +ricks-stuff +efficient-hashing@ +portals-relinked +asterion +safesave|安全保存 (SafeSave) +@hide-names + +text-placeholder-api-unofficial-forge|文本占位符 API [非官方 Forge] (Text Placeholder API [Unofficial Forge]) +hostile-humans-unified|敌对人类:统一版* + + +player-radar@ +@jei-sdfalt|格雷科技与 JEI 兼容性修复 (GTM JEI Startup Crash Fix) +sweatyfeet|汗脚 + +modern-trials-integration|现代试炼大厅移植* + +echoes-of-horizons +crycraft|晶体工艺 (Crystal Craft) +hammercraft-fantasy|中古战锤:混沌魔域 (HammerCraft: Chaos Realm / HammerCraft: Fantasy) +sophisticated-redox|精妙背包:氧化还原* +lunalaces-movement-overhaul|LunaLace 的移动大修 (LunaLace's Movement Overhaul) +lightless-dreams|无光噩梦* +create-adapted + +@carpet-aji-addition + +reload-emc-command + +keenlib + + +jei-guest|JEI猜你想搜 (JEI Guess) +stellara@ + +dawn-star-battle-preparation@|黎明之星:星之战备 (Dawn Star: Battle Preparation) +chronoclones@|时间克隆* +appliedas|应用星辉 (Applied Astralsorcery) +crafty-cards|方块卡牌* +summy-reliquary|七罪遗物* +hlink|云联* +coppermc|铜月饼 (Copper Mooncake) +secure-pouch@ +gouge@ + +oh-the-biomes-youll-go-legacy-edition|你将去的生物群系:传承版 (Oh The Biomes You'll Go: Legacy Edition) + + +armor-rework|护甲机制大修* +ice-and-fire-community-delight|冰火乐事:社区版 (Ice and Fire Community Delight) +convenient-equivalent|便捷等价* +refinedstorageaddons-fabric|精致存储附属-Fabric (Refined Storage Addons-Fabric) +hazens-archaic-hexerei-armaments +yakisugi@ + +disease|疾病* +create-fly-farmers@ +@create-better-wrench|机械动力:更好的扳手 (Create Better Wrench) +create-aeronautics-easy-assembly@|机械动力航空学:简易组装 (Create Aeronautics : Easy Assembly) +emf-compat-parcool@|实体模型特性兼容:跑酷! (EMF Compat: Parcool!) + +hp-end-expansion|末地生物拓展 (HP End Expansion) + +music-player-reborn|音乐机 重生* +torcherino-community-edition-remake|加速火把社区版:经典重制版* +beyond-emc-addon + +lfgm +oritech-smart-splitter@|Oritech:智能分流 (Oritech Smart Spiltter) +better-fluid-pipes@ +terrafirmaearth|群峦地球 (TerraFirmaEarth) + +world-settings-quick-exit|世界设置便携退出* +medievalcraftweapons@medieval-craft-(weapons)|中世纪工艺(武器) (Medieval Craft (Weapons)) +metal-swords|金属刀剑* + +saberforge@ +parallel-torcherino|无限加速* +librarian-1@librarian +chuying-proxy-play|车万女仆:褚嬴代打* +ac-core +structblend|融构 (StructBlend) +ragdollified-reactions +traceable-print@|足影寻踪* + + +create-fly-power-grid@create-fly-power-grid-unofficial-port|机械动力飞跃版:交错电网 (Create Fly: Power Grid) +smoothplayeranimations@|平滑玩家动作 (SmoothPlayerAnimations) + +@litematlist +obsidian-bucket|黑曜石桶* +framepacer@ +concurrent-chunk-management-engine-forge +echo-warrior|英灵回声* +echoes-core + +@curios-slot|自定义生物饰品栏 (Curios Slot) +carry-cats@carry-cats! +immaterial-drawers +tainted-tools|污浊工具* + +authcmd|命令权限编辑器 (AuthCmd) + +team-economy|团队经济/梭哈吧史蒂夫 (TeamEconomy) - 作者预览页,项目¨@team-economy|团队经济/梭哈吧史蒂夫 (TeamEconomy) - 作者项目, +lootr-liaison +enfc-exriconniours-foodscraft|ExriconNiour的食物工艺 ([ENFC]ExriconNiour's FoodsCraft) + + +zombie-legion-tactics|僵尸军团:扩展* +eeeabs-mobs-ascend-to-godhood + + +@deathcore|死亡榜 (deathcore) +aether-spellbooks|天境魔法书* +tfcmodernlife|群峦现代化生活 (TFC Modern Life) +immersiveaircraftmodernization|沉浸飞机:现代化 (Immersive Aircraft Modernization) +better-blood-overlay@|血迹覆层增强* +book-of-sins|罪孽之书 (Book of Sins) + +vaskars-remains + + +the-long-travail@|苦旅* +brnquest@ + + + +bundle-stash@|探囊取物* +projectexpansionneo|等价升华:Neo (ProjectExpansionNeo) + +simplistic-parasites + + + +health-up +forge-client-reset-packet +gregtech-6-neoforge|格雷科技 6 NeoForge (GregTech 6 NeoForge) +no-starry-drunkenness@|啊啊啊啊啊吃天体果实能不能不要有星辉酩酊* + + + + +ok-integrated-dynamics +create-crafts-additions-adapted +@stevens-mod-api +plague-disease|鼠疫 (plague disease) +origins-legacy-neoforged@ +origins-legacy@ +create-fly-crafts-additions@create-fly-crafts-additions-unofficial-port|机械动力飞跃版:创想附加 (Create Fly: Crafts & Additions) +chemica@ +@create-stellaris +forge-client-reset-packet-forward@forward-forge-client-reset-packet + +chest-item-vows|箱子里的小饰品:誓令 (Chest Item: Vows) +hardcore-itemstages-reborn +pouch-of-unknown-reborn|未知之袋:重生* + +enderpearl-backport@ +tongby-adventurer-guild|冒险者协会 (Tongby adventurer guild) +emotecraft-unofficial-port|表情工艺(非官方移植版) (Emotecraft (Unofficial Port)) + + +permissionless-tp|无权限TP (permissionless_tp) +charta@ +speed-display|速度显示 (speed display) +xiancontrollersupport@|显的手柄支持 (Xian Controller Support) +better-connections + +bettergregtechponder|更好的格雷科技思索 (BetterGregTechPonder) +twilight-sparks-delight-fabric|暮光乐事:重织 (Twilight Spark's Delight Fabric) + + + +@scratch +mystical-agriculture-omnia +@smoothsneakingmod +ambient-camera@ + + + +more-machines-formula|机械动力·更多装配 (Create:More Machines Formula) + +crafter-block-backport@crafter-backport +plants-zombies-skyblock-wheel|pvz附属:转盘空岛 + +ae2-screenshot@|AE2截图 (AE2 ScreenShot) +cgc-animation-addon@|自定义枪械永续动画扩展 (CGC Animation Addon) +@mouseclickswap +nifty-ships-biomes-oplenty-compat-update-muddyfork + +sulfur-and-potassium-1-20-1-backport + +spatial-gui@ +everrose@|蔷薇合金* + + + + +random-equipment-attributes|装备随机属性* +omni-display|全周天驾驶舱* +cthulhu-creatures|克苏鲁生物 (cthulhu creatures) +camera-inertia@camerainertia + +chat-colours-modified +3w'3qN3w63wn3vp3wS3ra3aC3d&3Y^3Xj3Nq3uY3u=3qx3dK3uf3f&3tv3ve3r;0012'+3TY3uc3vD3vQ3cd3ru3jK3P33w>3ie3nZ3l(32)3l$3uo2,33kW3fC3t:3rm3V*3pG3wK2zc3et3Le3uS2:I3T33m}3wh3U#3Wf3sF3rr3X)3v!2*k2}A2[a3dz2;53j!2j32gK2'$2R{2h:3e'3eL2pj2Zw3ow3pX3qj3d23d'34a2!P3iA3mW3m!3nb2yP3p=3mt3rO2^u2f*2$Q2]A3w/1[<3nc2@H3w<3VC3X/3iB0043K&3v>2Fm3mb3jZ3tN30}3oM39v3IP3v'3VA3wR3vm33B3T#3wo2fs3u,3HS3a73W*3qL3t{3OP3iI3Y*3fo3o33m:3uN3PX3gS3rg3v73jE34s3SK3l}3rP3mL3pz3Jp3w*3E?3oP3v,3T83iV2zk3u&3ZZ3E/2'd2uu3lw3vb31q3mF3CW2o13hJ3vO3vV3sf3o<3g*3sQ3E^3lH2aM3JX3dV2dA2'3B13f33O[3sD3j13qU3vt3wB3sV2um3f;2](3AC23AH3Y]3oR3f,3G*3QV3V}3uG3c73j:33l00B3Ps3th3t(39C3kQ3N03hz3Oz3d[3wj2}V3ej3iT3kb00C3nE3O;33a3XR3wy3FK00D2sv35E3iu2lt3rn3W635Z3R{1:63l?3a)3Z}3Cg2(a3oE3h13b@3E333/3JF3nN38*3t+3t?39$3P73MD3lD3L03Wq3R}3QX3i{3wE3P%3YB3e02o43hx3ZV3vg3w03Vd3j?3mj3sJ3d52&23by2n43l82@q3eN3ld3OA3Wx3n<3mY3mH3m$3id2:*3vZ3fE36f00E3X}3H}3D22;931V2Y73TQ21836i2a53mc3Rp3u#2pU3cD2>!2%^2[]3B#2w93FC2@T3tl3NG3hb2$%2#[29k3XD3U,3Rz3K^3FT3rp3TV3w{3cJ2^g3k93nA34,3TJ3pf35/2k<3p]3a^3ND35(3jB3I'2{)2+^3Zn3Ys3c?3GU31p33P2dO3mq2/[3Br3q(3r42$u3aG3q/3L43V=2z/3ei3e:3a>2=T3s=39[3I33te3dX3q*2:!3u$2B533+3903UG3OH3wb2@w3p!2>63k:3uK3kr3X?3t&3tP37U3rF3N+3m>2}C3ez3BI3g03sT3B$3dO3a}3K43mO3IF3h'25>32<20I2p,3Y$2MS2J*2im29X2&F3dG3aM3no3a[3pD3p[3l43re1>y3un3lh3o!3DN3Tp2!a2MA3ka3oL2[23fD2yN3qI3r,2>]2uH3Jk3T639/3wF2!A3l22'x31Q3RV3nu3le3bY3b93GZ3Cn3sa3iF3213YG2QY2gD2g$3wO3wQ3V13r73cX3VB3R&3Q,3LA3s53u%3pk3vo3i32df2iq3in3eC3NL3503M+2q}3t500F3g<3I:3dg3pg3GO3gC3C11}51!p3wP30/3H92f92?v3GW3S+3Im3cS2[(3j(3p43wN3hF3sg3vj38N2$p3TB3X(3hI2W}3h<2!l2PZ3kG3bc3Hs39>3I{3ua2}63bv3b037E3ef3vS32'3V/3Em39A3QT3k73WY3ko3rq3T)2&D34=3fB3v33B,2)D3oG3mo3w134{3s'3m63a22i03D,3Z82an2@72#p2kk3OW2XK3A+3U322Z(30q3aL2[c3FN3p*2mV2SN2{R3S02*t3bk3m73tQ2)}3rc3pQ3q&3jH3QS2{t2oS2;O3XM3NM26t2o02u!2^!2q#3XJ3l03t#2x>3kd3s73hZ3w,3j43Ek3XN3JH3Vu3e*3Sf2%O3q)3mp3cA3TH3jl3tL3ku3T13Kg2(c3td3Wk3wa3XE3gW3vE3eA3qH3c#2jI3LC2^M3o>3vc2,83j03W:3W+3p,3nP32M3h23Qi3dB3s:2p(2*s3u;3P>3to3La3f%3KX3NX3w@3Ya3k83C42ta2wQ3P03fb35z3i22^s37W2VV3q}3c+3wI3gX3w}3ql3PN3ci3WL3fO3Sj2&93UF3rd2[D3oi3M42?93Z33X$3VJ33>3s83S83ct3X83uu3l{3oh3L'3Zi3JR3N!3o53jh3T'3TU31E3Op3MR35c3c[3C@2)n2)$3d83N>2n73rZ3kL2ZM3YN1;11'93lE3gG3Co3HB2#*3r*3pK3P}3g)2:y2uN3Ne3RP2@/3FD3Gk3ME2bl00H3Px2BV3F^3ov2TK3Np3hR3tX2c!3Vz3ic3o)3JZ2;F3Bl3k33aQ3el3t=2'k3O^3tI2;*39m3S)2%*3FU3up3bW3k]3tM3m%3gu2l'3uA2?x3R*3gx3v23U]2?j3YR3pp2:o3TD2XX2!#1{23gp3HH32^3it2pJ3ut33o3Qa3ob3n13pb34V3Lx23;3l<2?&3EA3e(3TR3n&3QE2u53c'1,x3ju3vn3TE3Dh2Wt3Ad31c3gf2%,39P2^c3fl3uy2Pl3Xz39a35<3L73sr2]}3vM3jz3Kd3q@3I]3dE3iY3u<3b/3A(33y3ZY3vK3cH2!Q37r3al3Au3pm3XQ3F63YD3143Gx3dW2%T2/y3jy2#=3W@3dy3T}3RH3Yo3t23Vn3Kh3Q(3gr3tR3dD3Gw3u62RA3WO3hc3kN33=2)z3Pr39e3eW3Kb3Xs3RM3fU3JV3Ld3j<36?3Ok3g23v^3j[2^'2!43nf3P93KB2!Z3Aj2,d3ZF3m+3qO2'M3162&}2r?3Uz3c}3DJ3VV3Jr3Yc3cI3pi3u{3vf3fq3k)3me2(>3B[2Fd3aV3r[3M%31U3GI3Xg3YC39w3tG3dw3q$2ck3I53io2>[2+j2(83Xp3n63H;2L73d}3Z02oZ3nB3I?3Dl3lS2)B3Ag2=H2a^2U$3J&3LU37G2(Y3tb3k03qw3fZ3iz2RZ3Sd3ex3293ch3b(3jQ36^3pB3ca3T?2g]3u(35=3DM3RU3tu3jp2#83B%3Z?3nJ3r]2'=2sr2TH3v:3rT2t?3By39i2*T3K'3nr3aB2aX2#q2[t2Wj32t2^e1vF3tc3YQ3k62!C3I^3ib3Hr2c(32]3j>2vA2+73fx3rX3PD3jP3ip3rl3h%3j#2[p28f3n$3Dc2uI2/p3if3H43WE3kv3o%3TI3v%3EJ2dl3Nr3Vm2G+3WF2ms3bV3Mx3o,2)Q3r%3u33we3J%3wp3eE3R+3i%3bn34l2'52wj36+2C(2Q42{T3dx1(<1{{2GU2ja31I2XI2[j3b:3BF33M3S;3n93YM3W33NQ3Q73Sy3Kv3P,3t63t92:A3693G)3SS3s13mg3p52n}2>^2X22pF3X52xA35L2fv3Vg3882vH3qM3r@2v(3DA3wA3RJ3nF3YF3LV3Z53wl37x3K:31d3b83eh3qs3p{3p<2?L2/a3U'3r>3v53PI3l33Q/3gn3QA2K63vz3Wc3313hA2Tv3W&2h?3273vN3M&2+L2((33*3AM3kq3m82yV2$e2r63Is2th3o?3N#3Xk3U$3K{3Ee2e,3q62@+3HV3h93i63W!3l+3Gr3e730P34Q3IT3e=3Sq2@#2n]2[;3QD3f]2:[3TP2v13rh2=N36L3jA2yU3wf2Er3qQ3Fr2SG3Ed39G3C!2x^2>/3X!3Q*3Z#2T&2z(3pE3cQ3Rx3JP32I2;82K93hk3Ig2b$3O!3Fc2,v2;V2rx1np3bG3j52=P3Gi3pS3V73P43iO3hX3wL2Tl39,3od3Y13mr3lj1xP2MY23y2>;3m;3Vx3i03iN2=X2x63lz3lY3jR2b93n/31j2qq3qf2u3U=3973mI3Vp2,'3aJ3Po2Hv3NK3m=3uw37s3c!3sl2{g2jP3t>2Iw3mU3J72Wr2/;2vE2f<3m]3w^3vC3Al3qZ2<:3OV2wM3t03lr2&u2Nr30,3vI3r!2QM3Ol3Ls3o[31l3a<37h3af3WS3ok3sR1}F2aU1,b3Wy2kM3o/2nd3Q!3hK2)F2oG3M72v'3Gm3s33E%3ER3mN2,N2}*2*J3qF3Wi3t^3uj2$Y34^3S:2#s2'V3Ry3lX3a'2,X3X%3kK2i)3uJ3ag3jf3jV3rx3m,2xh3S>2)^2P^2tf2,l3DO3f[2r82[,3l@37,2x#3Qm3r92Ri3ml2lT3He3P$3tk3J<3g92xt3Up3d13CF3mh3n#2Q3a62[m3HJ2s53ni2^p3m)2K42^[3mn3em3tO3Zy3SR2k;3so2bH3F33BJ2[$2[d3a=32G1:Q2%[3f/3R'3CN3g,2>t3eg2)E3j72;e3WP3L]30f2+V2*P1$w3q'2W71/g1xM3r'2)132b3kk1Wt1L(1^N1'u1,s1NT1qj1dK1Sf3ol3D32uQ3sh2+l2+w2zW32+1u'3l)3Ac3tz2=y3gv20Z3Gn3sX2uh2't2+<3W;3723Qv3nU2$I2U>3p23uV2ri3S[28!37<2xR2IB1m02]F2hR2YY2B82tT1&+1'm3cR3R%2hF3qG3h/2*g3wv3sw2R?2,k2sm3pu37;3Zz123KC38+2*M3h&26138e3sS2bx3nj2Z%31K3SF3Zw3WK1qQ2ON2k#2pi2RH3SE3p#3dh3h$2wG38:2V$3Ey1lp2p?2'32q<3jo2A@2,[2@C3Q43H>3M63c82!@2@E3OC3DC3F+3hW1hd2m=3c:3Y:2rm3ZM3UM2$32rt3b$3sB3px3c{32!3091j@2+%2*@2MB34Z2rh37C2(:3j82c02Md3W13B!2Su2%s3d#3u[30S2%i2V03i'3t83fI2yF2)V3g'3Ot2z<3qT3qi2]:2cc2bs3m13b+2:h3En2Ni3XV3VM3B{2i,2i?1dJ3KU2ME2iv2jf1;w2a%2TV1v62Uo3NO3jY3q43L@3FV3U@3Fa3p}2zt2n51g82Kg33[23i3Lq3fQ2P[2w63C>2[<2>U31/2nz3RR3cc2Uw2'P39K3bg35t2*L2}m33T3hh30v3V!2'!2fJ31;3GL2dE2%42u}2eT33n2hS2Wf3U02rO2=43Jy00L2/72bt3uD2ur3a{2hN3O$2Y+2&q3Pf3pW3Mh3J^2z!3f53pM2w$3w33n=1*E2r73jI3RN3cW3c^2qa2>n2}{3Ox3Xn3Jh3ZH3vG2bL2X(2el3OM3O53g62DZ2*E3E23v)3m{3nX34k3lv3sY2[A3Ii3HE3s43SZ3OD3HO3M83jJ2Db3U(3fy36:3Zq3hy3gd3lT3vU34e2u*3X11]>3Dp2*=3Hw3wt3vi30U3Mf3UH1#)23<2XV3Fw3JJ2,Q3F>29P2TP2aE3g(2Vi3M500M2%q2,I2(g3ah3Vv2<&2%c3IS3BZ2]m3P&3bm00N00O3Yb00Q3d]2tq3KP3lq3DE3Fl3YY3ud2v62p931v3bO2:)1x[3i+2*Q30C3qB3tZ3VW2;x2SJ3p$3rE1=S3r82/S3L:1)q00S3oK2]N2563Q#38B00T2DG2tu20u24=2nT3Kl3AG3MZ00U3rW2gW3S*3NN3JS3s02[33No2,G2=j2v@2)43lc39]2kG2vq00V00W2mm3rU2uA3gw28i1>62wf1j12?F2343ON3SI3mQ3qP35k3dQ37K2ws2Cw3p+3g/3Kf2;z2}o3Zd1>z2t!2>S36u2=*2v52!&3oZ3f?3R=3V'3kx3RZ2b{28c3E]2O72+P2^13YI3Ch2A02s92:P22^2Nc24V2(h38g3l&3bz3VD3wD2Lm2o^3lg3V52N42m!3qt2/i3sj2;i3mZ2*n3Xt3e,3bd3M93vL2}N2^O2T^2YZ2g?2dt32U3Fj3PP3eJ1kB2Cf34J3q035U3V[2Mz3gk2rj3a,3HF3G93Yu2q:3Jf2IO3B]3SY2>:3M'30%3p?3R500X36m2y62,q3u13g+3TW2+b3ep3h^3fA2iB3Hl2^v2O&1;}2(}2pz32H1'P26Y1i23Eg3J$27F1TI3OO3cE2Z)3OX2M:2zZ3p>1p+2k23b,3S<3gP36t3NP2$J3Wd3RK3u@2<93WT1b92<'2zw2vT3mB2Hy2ZR3c&3CO3iL3R#34;2k42Eh3e42jx3E=34831'2d?2%l3Nu1$639<1mp2{[3Qq3YL3Cd3Nj3v[3Fe2Zv3AB2A<3B831>2I}36a2Fp3cM32j30g30>2v%3UT3n'2kg2c=2du2**3r<3s939R2%m37^3Cb3823G62?33EW3b<34$2s>2aO3Rs3p13lF2XZ3iU3S631R3Gp3rQ2mg35l3p@2#Y3K02{]36)3JA3IV3AD32Q3YE3ax33L22D2hm2rC38}3I%3um3gJ2(d2yo3b73BD2S!2)U2>32(p3O72],2E[2)<3NI2^@00a1b@1w23P]3C?1q83Dv3GK2Yf3vx34y38y2]P3iC2Ss1)&3Z(2TU3593J#2NQ2v42q$3ly30l2{83j'2Hz3I(2I<2@42(92pc2mM2CM3KD37B2fE2ue34&2HD3rk2'K3l>3Ja3Z:2s)2fW2d#2@91td2}v38,27}2/q3w#3cu27T1i,3Z@2]'3a$3Sb2^J3A^3LE2!c2)'2LA3Kj3i#2'a3A92r%2ff3R72Xo2g<2+53k&2[l35S3WW2G=2)h2>o3Yw3LQ3pT2fj2q13j%3H(2{{2bz3Jj29t3h{3t%3i[3ik2x$3gE3F#2fP3Mw3S72fh2C92RQ3Hh3QR3Zh2{N32B2;[22:3Bo3uO2012$G3i:30Q2')3Ic3Kq3H237j3fG3nK1,33Gz27U3AN3p:3g^3Ux2uY3B:2xE29h3nT3is3h!3HT3K>3DQ3Tc3os3ul3tW2BK2:M35}3S/3Zp2fw3bM37R3WD2w13j$3JY3oD3n43W<3ad29s3WQ2,U3r(3Eq3h;1SV2N{3O%3l,2:<3wm3qm3c93oT3sU2!%2j[2ye3IW35i3dj3763n+3vP3A839?3c33s!2:$3L>3f93e?3rH3rD3rA33g2]K3mm3R]3G738w3k53cN3J13US3qq31a36h3n:2RS3Y=2,92'r2$k2uK34?2ph2@23OR3pj2)63mS3rv37/2;Q3QF3nM23Il2+'2w%2=x3171iH3L53s<2D^3L;2^t1kq3YU2,c2&v3vR2bb2qo37@21S2zS2tk3e>30K34w2:&2rH2/'3C22=[2V!2KH2]J2Tr2zv2;w1'p3je3i&3b!3oq1*b3kO2FU3rf3WG3U)3Z%26R3fm2:q3SM3M@3T;3Mg3OJ2fz3vv3W#2(f37H3I+2$<3O,2/)2G12lO2%t2#r3pc3NE2:@3wT39{3Re2}^3tn3H=3iE3Sc3r52%u3123P(3at3s?3qK3eb2T>3Ke38%2n{3pZ3h:2j938Q3C{3J*34G2MC34f2{<2Jg2}&3Y!2:e3c*2Ob2,r3713a?3Pj3GQ1l/3K*3WC2(H2mF1*(1m92Me30W1:d2#R2=u1#Q3V32?X2Ag3YX3ls2P33kX3v03K]2{93qe3e^2^j2{:2NY2b&37'2L43hH2={30e2q(3M[2oy3d32qI3S93AS2uZ2a#2?G00c3Y92GZ2@u39!2$d2PK2rW3861q:2w#3wM2@'2Hs3K?3fk27J2/w2XF36!2}82VD3cP2^k3eQ2nP2Nm2i<2^+3Rg2Fw2#U2Ol3bo2*i3b[1?$3EF2x12&%2%+3qu2I13s#30R3Z{3gB2Vc3M}2'83g[3fX2t,2A'1?22A!2rU3Sp3I)39%2'z22T15M3UK2X92Gc3Tb2&R2S^33j3ss29#2,#1<73Qp2M$3rR2D*28j1jn2y&3Yj3aK3LF00e2/b3fF2%v3lQ25i3l633^2{j3V63mX3Xf3843s{2lr2f[3bH2:u3K=37)2c%3ba3V839138c2ba3J,3ID2vG2g=3On3l71>72*b2?g2?53UN22Tb24e2Fa3sz2!N2{?3Si3Hc2]!2]]3V$2##3C[3VL3su2K*38'39'3s)2j!35{3UW2!,2]n2CT2(42oa2=g3i?31Y2mj2)028d1jk2}[3fc3O93YK2?O25!2p;3BM2h53EU2nb3nm3X@38<3V03bR2g92T12Q(2Hp1am2o?2x]2F52ux2EZ2E43as2{>2#D2lB3Z^29125A2}t30(3Y&2jU3XX3LJ2nQ35R3jO3Sr3o$2tY3G;2,!2{m1!Y2Hi3nR3e+2Mv23X1Jj2zL34X3PF3YW1^(2VM2Fe3F}3la3tt2r<2Sr3c(2mW2O$1C*31H3R33oz2!=3i(2Fk39L1o,33t3bl3X43QW3hl1bq2BW3dI3Q92Ch3Z*24S1Tl1#G2'b2KD2vJ2*)32k2wL2iz3i82C]3Yk2eo2?{2vR29*2O42wu33}2>+2!b25u2<{1!$2v22c{27d3km2!k3k@3OG2TA2Co37Q2GR2cT2;t3lL25<2!)2Z225'2};2lE2ho2*K3p72M52C+2B438;2eK3kf2(v3992YE2n<3Ix00f3A<27300g2+R32:3DD22Z2jN3L=3CI3v;2d<3Yp3T53H[3QL3An3bi2!q2?W2;h3f@2H633'3I}2]+29d1w(2Id2}#2v,2w:3Bw2c*31r2>a3qg2Tz3Yq3J)3H#2y]2FV1(S2%n1vb2Q,1{w2p}1*W2{#2E^2BY3ro3BW3ZE2(s3mw3L!2V72Fo3ue2'63Dq3W,2+r2}P3w53133R42&K3Ay2j/3qC2;K39p3Q)3BA3Ga2nG3da3q,3es2']37n2NS32v34P3Nn2Nq1@O2%H2*^3ll3u72R32Z<2T53pa2]V2UN2o:2m13Y33N?2v}1(U2hf2?$1[N3W935x3Mj2R>3se3hv3Z73G}2iF2t*1Yl2^?3K;3D#30N2dS3KK3Wv3Do3a13jg2$'2u73u93BU3N'2^^2#}2VN3A>3Y832,3332!y1!D2>$2ts3Um2^%3aH3UD2Ie23f2<$3lR2%82pG3fi3b22Zx3M>3Zm2}}3Qe2+T30o2}y3A73kY2YJ37>3F<3a52wg2Wv2>e3Uc2^>3U12Yq2Pc3C*2X<36S2?o2?@2<63LL2+z2nC2yE00h3bf2@$3AU3bh2?B3dU1;o3WR33v3cY37z2/,33X2vs3Jc1]136p3e83Ob3SH3S]3gl2)Z2(72W22;P3D51,o3m'22:}2u@2z)3Rv3bX3dr3S^2JV2/j36n2@Z3eB2wn31@3D?3Wr3Ij3OZ1gF2753D72Y43jN1&V3KA2%U3B;3nw3jj2{62/12g51yu3c53H:2Cc3Fd2([23[3ff36/37i00j1h=2zU1>b2%Y1w,2N:3H<3Tj3WZ3m@2]<3rN2wJ2yu2;41P73TF3V:2s{2[h3Q%3s;2LP3E*3Ak2p/2%(3F82k32'h2s03JB3ne3i)2<)23MX3tS3XU3q{2L62D@3N(2ep2t&2p62:]2pN2>V1*)1{I3Fx2aN24B2k+2]02?D3I=2;Z3G52qB2]?2J<2mS2x*1:h3st3i93Zj2o93d(3Z;3B(31X3Wp3t33aO3XO3pl3vT3gR38u2R03EO2cl3M!2xq3H+2*e2pP3p63UA3E73kz3QM2#%2#E1]31p=2JX3T03lP26O2Fj2Aq3CT3r33cU3M)2{p2,%3cr2&W3Z<2XP37V2r$2ZD3Uw24j3L83f22C!2*>35u3tm3Nl3WV0,n2+s1,D2e21>n2lz3Cm32X3H72('1Lc3tC2bK3Q03A,2sR3e[2';3Ro2'230X1Ni2Bk2@}3Q[26536=3Mu2/22x'3l52d(3aT37T3My2yX3P?3g#1]&3CR3Cj25Q3eD2m)1PM20%1ZV2De1^n25(2D?2$(3Z11vj2432k$3MA2Ph2#b2Ct3F%1}3W02'F3EH2jB1xv3qE1#!2r22<[3cp2$=3Xx2>C2(x2kH3W$3c03R!1Zi1(=1hN1]:3C]2QN2S72#c2Z/3Vq3Fm3u'3Gt2u:3Z43Zg2X>1cq2*?2t62)G2sO2oD1Qp2A*3Yr3o(3cx2^$2L:2g,3c@2mX2sY2gS3e12o+38j3MT28%2lk3MO3Va2>)2=#3aY3fN2/e3S,3gq3Ll2WS3O+3v&3B53iq2nR2;E3QJ2{22)l3HR2]>2,e2qm3T@2Yd33s35]36'3Lz2P63EK3h@3Ep3Eo2'43W83f63i]3qo2[)2e=2$O2r527O2DN2)?2'e2z=3NB2[P25Z3k430:3eY2Xd2,K3h,2mh2n*1VY3lI3Lu3Vh2zV3Ty1$Z2/P2}w2z%1Wc1'X1YS1^>3dA3V^2%933U33F2H%3C%3cy2z42eI2;#2iy3h=2v<3bb1d*2f12)I1Ll3W}1}o2Ta3Q^3W53ia2$c2,j2%g2z;2+S1e+3D11@(2>F3u53Q@3N[3K)31g2ls2!*1cM39o2ST37&39z2E'35b3l;2>m3Z>2{s1Y62l&3ts3Bs3vB1{Z3F02ug33E2aB2=V2D22@31(/2d12)w1:=1)/3oO2,u1%334[3aw00m2N83XC3mK2Z83FA2)m3Q;3Wt2cs2[W2S@3bx2m[2:z2)52jr3ki3Uf2^A2&435^2>O35p3Wl3J[3d%37[2,p3KR3872JT3U/2jp00n3a93g}3Zt38@3IA31t3Y'3M/3SC1d$3GG2MF38o3t[3a(2yI3lf2(L3Ws3ns2>L2ci3eK3FM3Xw2$+2jY2>12;j2X3c/3WJ1[G2SL2'&1&}3Vy1?x2}12by2jL2uf29Z3741+=1#a2,22zO34W2p^2][2/Z3jW2bA3AT3p%3k>1pN20;3ao2@=3hL2l03aX3Sx2<]37*1sE3sn2;M3Tm3D'2J,2mH2kA2<*3L63w%2:'3gt2iG3Qh3Yl2,C2f42x23eI3Td3Za3Sg34A1uK3Pp3hU2cK3Df2[U3sN3701?g0,G3hf3KM3U73dv3Ge36B1?@3W21w&2JW2S*3RO28B2+H2vm26e35v2EL1@'3Uv2Yo34m2Ak3ea1hy3Jz1[P3og2xG2:a2#o1}'2A33ks3d?2rw3KL2I#2&y2An3Eu2/{2V=2$F3Cx2iJ3Ut3O&3VU3IN3iw3sd3Ir2$M3Me3P[3n}3BN1$i3l=3j31,W3UO3lC3R83c;3Ih2[H33q3Yt2ly2tw2E62QU2y)2V62Tq24y3iX2[03Bq2]42^:20U2j]2@e3L?29=3gm2)Y3KW3G?2O:1tS3uC2S03I93PV2!?2;)2J(3q13612+q3n]1([2%A2)N3Q'3L93Xv2<#2}223{3U%2J^2ei2sV2#k2TO3FP24p3h>2?:2(F2B>2Jw1z'2ZH2Wz2Vk1MS2GA39l2aa2,A2+D3C:3522023ET3XW2X!3m(2YS2>43aa34O2wE2dz2ES2bW2&T29D1yM2Zf2Fs3Wz3Ds2G*2J42IP3R(3PL2oU1l;2(n3p82Gm29c1rU2sx2LB3N]3lN2bC2+U31A2{f3M,2tv30w2mc2jl2U@1GK2j*3P52NE3V,2S]1^H3CL1:w1[?3u03gH3bI3cv2G'3V@3Y)2nE2E+1J?2PM2QL1&h30s3A!2Ww3022#C2i#18K3ZX2;f2#m2/g2oq3oY2uv2u13393rB1n33Db2,x3E500o3E:34p3Q>12l2ew3o#28}1we18/29^2T21cV2&M26H3eV3uE2d*37}00p2wP3LX1[l3ar3A33d013?1wP2#>2dC38&2Fq2w(3MN2Tu2Ql31w1,f2&H3Xq2AJ1%223z2a{3ce38#2'Z3O/2;@1}62*{1z!2'>1[Z3i=3cw3DG30M36y2;>2Bb2;d2JP3To23730*3f'2lv3ir36&2QF3O]2wC3ed36[2lh2^H32m2)+2x)20W1{x2ec2Ox2Ym3dc2>%3o73SU3sq3np3Y[2]O2By1wA3vr3U:1la2ee3jn3gZ2C531h2F62B'2cX2]e3L23mP3F23CZ3e<1yJ35+1[q2{A3cs3jT2>G2[q2@d3J{30=39F1Id3ZW3MI3UX2f?2h@1S}2C'2^w30[2sW3Eh2!J2V'2Z12T;3h43O)3pH2q{2(;30u2b}2cn2!v2n>2wV2Ai3G'3ot2?z3fv1v718}2Ao2@N3D[2Yb2;62kb2yH1Wk2$f3Pn2=)3TM3033Kw2f#1}j2,M25J38]2W43Ko3L*1pu3s[2av3or2*w2gC1mN32/2o>1sD3SA2]Z3Oh3243A=1qn3s>22>3eR1q&2q734b3D;1tD2Tw1{S36j3Kx2s12#^3Jm29H1,B1H>2zq1@W1:)2L23f723'3cq2i41bn2h%3Tw2*82W^1kM3gj3442OM1{u2wb1=T1!:2wY3RQ2kI3MM2D(1)z2^<3Ye2332U=1&32X71{B1WM1p}1IM2@*2h{3IU3UY2rZ2Hu2!h2rk3252xH3nt2(b3B/2,W2u[3VH32f3bE3Q+3f$2>M2T=3L+1102OB2]z1+Z3PE2=h2<3P#2TE38T1UA27<1vu1&f1PN2]c2B03ZL19[20D1RV0:71t524,1+<21s21W2e82I)2D)1,i1{a2%K2;}2Vo2o;2g82md3oF2[%2B&3Tr3Z,1+@2+G35q1Xv3X>3S{3IR2JC2}X30x2OK2e%1@)3Sn2(Z1y12[C1WZ2D#2'}3dn3Tq39V2G41C<1sh1*l2uO2'12g21;e3E!1[T1eU2<+2452Al1P03lZ1+H2gQ23x1M!18^16m1Pp1?71/{25)2AV2sG2]T1+E2nI3a!3Sw2Ys2c22Vf2:=3Ql2kt2U(2Tx2q22hQ2he2c[2r>12W&2:s36s2^&3Ji2O(3XK3CX3ji0>s2{,3H/2!p2/L2L;2lZ1]41{C1f51nh1)a1>U3Ml1,*2t53H53WX1(k2=;2%B1q<2hk1}x2%=2M72$U1@D2(P2'v33R2n629I2#e1uA3Qc3b=1>T3g53D]2FL2l;2S(1GX2=E2XN3Iv1zF1!I1{]3I#3k}2%f2{J2wi2hJ2/x2(533%24m2hH28+1c2{&3A42Td2nL2'i2GV2rl3G!2]C2T63C$30Z36W3Xu3nd2iE2bu2KE2eh2ju2mI2Kj2fb2lN3m93B&2WT2;N2qn2/=3n[2)s2yf1ck1bj2;b3jC2Ip2:D2=52BF2R!3U82DK2Qu2![3G]3lK2Lu3LM2lF2uE2B^3n@3XP3MH2[!2&>3Os2x(00q3pA2l(26s2uJ2i!1uO2v73NF2]X1#11:H3N;3LG3LS2$92Gk2we3C&2#&35s3m32di2cB3ZT3CJ3F42;p2jJ2!F2]j2,S1;@32x24+2qF1pb3U&2my2dv2981#r3O:3D83rw2z81{Q2UK2aF3R;2Vw3U{12d1Xh2)93T43Hj2Tk2*;2kY2Pu3qV3D:1s}3Nw3nC3CU3lO2x934z3H{1@/2kF2+{2/&3HQ2m+2za3Y/2gy1ah24R2o(3j/2cE2:l2nv22A2Pv38W3e/3C^2bO2@f3Y{1Z3az2[O1yi2IJ2(,2a12#)32l3Wa2!{1cy3933l]2%r3463OK36#2tW2:627w3Ej15%3H02H$39x2bh2322*j31b2vX2N!1]Z1d51]A1BD2i=2ae2D%2?>3j+1'e1}l2)(2<%3uU2ym2{Y2#61IZ1Qn1uJ2xz3R@3363cG3pJ2mP12m]3S333<38P3N@2=k3a@3Qu35j2nO2Cu1x+3CP3Uy2OP2GE3T{1;S2D2[/2&k1te34S3ha3Vc2k51,w1G83kn3b]3Iz1Wz1,I38G3J:2!G3N92I&3b62Of29T2Z}3Bv1XX3a+3aP3o'2ra2XB2lo3SQ2,+2z{2Ca2yT2^x2$#1}Q2E$23s1Eo2;/1L/2?(27(3l^1XE2da3KV1yv1E50>*1'Q1cz2&l3S&1d61,]1JX3Dt2/?2CB33h3Y637M2I;32D2%D3Pd2bX1?A3f^1(H3X<3am3f82cM2W;3n%3m41]L2P$2h}2@R1GT1Po1y/32p2392291EB2Q$1[n31m1qk2WB2T(2x{30#2!r25I27b1(j2pt1#p26M1)^2?s2j2A?1Q]3qW2}=1yj2NW2DW3D*30A3Lo27t1DW2!13G@2ic3nH0:A3E(3Vk1+K1@#2{h1<%3K924Y28[3051R]2]%2VX2xs3J/1Y[1Dk1?32M+2u=1M61cZ2@K2UX2Hj3gK3kD2xJ2T$1*;2+[1ut1W{3D92j@0}21'a26$3B?2Vy2Fh1TY1v'35O1#y3Mr1)w3g33FF2qU3a03Q?2Zq2ka2t;1K63ox2A>32c2E{1q(2d^25d2il33H1uB2WX3n)2^X3W42b<2ZW1Ex2VQ2U!1LZ2I+26u3dH1^w2yK1aH2?#2+12vg21L2pa1iE2qG2P&2I62vp3Rh1g:1x91;<1(Z1841fg2QR1qZ2}Z3PB2Xa35A2D,2x422*2U^35X2YP00r2;,2!K2bT1452Va2b'1DA2q^3L}2K#2z23011be18T2A=2wH1Bl2q)2]&1b71L'1dt0[)2t{2oQ27Y3C62jq3cm1aD1mf2&e2L$2Sd1B/1Ve2iN31N39X1So2=D1y]2+M1UY30p2ht3YP3Zv2]p1t%38m3m#2ZV2M&3073We1xY2J51a(35w1@s27y1>*1;d3Fq2},2pl3XY22q2)21sk3hj30?1l#2XD35d1<}1ph1tZ2@o33I2>,2+f2z*2G{3LR2m&1b23Ki3iD1To2*92)R3nk2]g3i^2Dn3C)32N2o[1*C1pB3IO37:1?{2Br3O*2q;3hQ3qp1M&1zR2@J2wp1ra1;%39)2#I1VS2!^1cb37A3gg3Nf3VP1oJ2W@2=J1:y3o13Pw2P,2&a2Wb35W2JH2'l2F@3OL1m?33@2M<2S62Sl2ox1l*3003Ia3Vr2401=k2+82x}2gl1XN2xZ1nG0rg3ds2hE1,e2M'2D61+(36U3c=1gZ2Kr1}J2:+3QI2EQ2Oz2WK3Qk2r!24s38U2d:23^2Ja1lS3PA1>a3r&1}#2>E1M]2492eN3bq2n#2Oq3Vw2/>27[2k{2wt2N$3o93g$1ad2hz1q]1CA1sn3Q<3E42':2Ne25j1lQ2eB1eV2r/1/S2C=1e?2F>3ge21J2F+3oc2'#1M^1HA1@b30G2}E2fd36<2(z2(N3Qg3Cp3Bj2f&27$3qr2zB3k$20m2J%1:<25w2y}2QV2//2R*2X2QS1#h3hC2oT2+E2Do2Ac2^I3iH2KI1a$2m?3hO1'>1wn1]J2142*o2DX1]G1rO2L@1WA3gQ1,O3Mk2&&2Q#1*w2t>3A{2LI3t}2El2LL2qb1kt1FI1pU35o2^f3F(2c81$[1Eu1YF21z1(f2Z#1H}1$j3s$2Y*3Cr2qv2031+]30r34I2%b2tS2>w2;U2,m3981Zv2A13Of2Ko2)@2IT3do1):1a%0']1t}1!!36w1w%2N^3eU2u'1$k2{n2'n3L)3GF2;{2kU38i1<[2e>2j11oG1gl25L25z1XJ2Cs23A1ie1wj3Us3XG3Ou1qd2&f1g$2uG3P+21(027s1Hz1Zp2Y82u(1z32]v1SB21X1{R3r12BC2jk38I2<<3dm3Bc1#k1sI1ud2Zs2bR1%R2cp3T:2va2R<1=n2Nw1&!1+92b]3N40'C23(1V31fr0[12:02#J2Dg2Qi35)3dk2;C3U;26m1:K1jp2}d1t02JD2Pz2?[3HN3NH2HB3MG22/2Pr2hq2%M2/92s#1]n2C,1{71JQ2!:2Qd2?830d1(p2l134x2<121i2z:2lu2h#2&c3LW2821fJ2H40;p2RE3c41'c1*/2ad2PX13l2D&2sI0#72=a2TR2ke2qX3O}29)3R,31<2Vh1h<3h02Ge3aZ1XO2)/3HG3R<22I1tI2PY1Ax2ny27;1tg3Es38X3S13Xa2u62%o1Vc3ZP3Ae2r{27:2@Y0^M3dF31(2;R3n^3Pl3w&2kq2{D1Bp2QW3Kc1902ZP1ET2!z3RA12$2&/2r'3R:2M?2NP2:22CN3FE2ze2mR1wf1=<2R$2U}2KT2h32E72&J3VZ1(b2fB2c+2qt1#s2#L2:S1+S1Na1my2)O22h2y,2}(2=/1+p2jD2992Sx1l32iL1Rb2x,2ia2=r2>>2xn3I$2Ov2J/28C32Z2h[3X:2z$3oH1ZN2JI1Z>3hs2M>1G3Z[1)N3dt2!I2uP2Wc1O!2sb2Oo3Hb2Is1nT3D>2V@3ty3n{3M<1%W2:;1}*2NV19g1;J1h42r^1+52ZK2yZ1+:1Ev2,60,T21'2!72E*2r=1'12e*3gV2r@0&11si2:H1Tx1Ps1zH1Sg1uf2'Y3Bm1Q:2!<1FQ1rC1Ks2H52yC1Ch1B,272;u29636O2;c2{I1>[2pq18(3J?18N3L[1yR1AZ3Ec1SS2Ka1bt1qY1h>2yt2ex1ZR2^61J{3Or2+?2DB2Qw2m#2801kN2RR2Kf3vl1[B2xX2f^1y21//1rH3DT30Y1&l1r43E>2'G2wF34n1UH1}f1,M2X$3bj2,o2J)36K3Mm1w^2N61[&1PC1xg2Ue1Hj3h*2r)3LY3V>1wt1o;1Xq2n11/w3ps34g2lQ1v82(j1y,2b%2vO1%c2v[31B3T(2zJ3L$2%$1};2xi2*D2q%2S%2$[2C22;'1m/1X<3J;31)1bQ3d>3K62$N2;<2;r1F62{M1cw2^o2N/1tV2j(2zK1^I2f$2)q3QQ1et3QY3dC25a2$]3nI2gM2^P2]@3hu1hk30n2{@2O)2Xz2@62pS1;U3202v82Wp2Wu2!X3A}21k51f71Y}1p02:j2$B33]2t[28H1Wu2+g2Ee2;y2H^1ft1$e1))1(731S1%k2&P1%@2$l1+l2Z43Yg2FI1p$3L&2?N2#]1]=1:+3As2*02=A1$X3V)3IY1iu36V1{n1,:26d00u2'O34:2x?2+Y2D72V91K72rP1m,3C#21Un00v1hF1>V26^3U*1S,1l$2In2[12L>27Q2'N2&;2;!2!31bd3Aq2JZ2tZ2<@2=v2gz3V#3H!2Te2ot2>92)J2@?1&K2V}3C721xr3t@2O832O2XO1j:2N'1ur2U83K$1pV1i#2Qp3k=2XY1w>3jG2Rk29+2J{2s41Jf3p&1)D3fY1)c1<51v?25e1o42a+3lm1O32]Q1W@3EI24o2/C3MP1Q/1C!3Dz3H11%D3py2QZ38a3iM1YP3DS1QZ1]235P3O(2%13UV38^2^a29N1Kh3l91w)24^1B{3c$1mg1>o1=h1o$1ou2&70,;2gv1:i3Er2>g34038[38E3Nz2[63Aw2X/2Wi3GC32q2G&1qA32>2sg1]%15m2%J2rD1p[2S{1s:3Nv3d+3G81'53lu25q1/k1Ea31J1oI2^Z3It1#52R:2z?2pK1{l1R@1C(0,13rV2mz1S)0[k2dJ2N}2$R3Fy2CO1[42T:30V1,K182aJ2B19<1H11m'1}i00w3G[24G2Gx3PT2l}2SX2u+3672xa1t{2K;1,S2I=2Pa2dy3R02XW1P21461273Iq37u2CA2kd2Em20X2Yn2Th2gm2vz1{<2>y3Y(1o{1xZ2Q'2Vd2FZ1Tj2@)2&Z3Gh3ZJ2L(3Pi1#}2HR1YQ2&*2(M3852LF2at3i*2P71eB2$,2Y11@=2>c3JT2SZ30m3q]1]}2@W2ek2(i31G1/j2;l1}k35>2xm25c22N38n34/33b2%V3VY2/I3S=0,s2U*3dL3OT3QO2{r3Bi3472&C2[J3f*1P[1^q3Gv2z61iW2g71aE1r/2123T!0$)2+m2Qv2v*2}e2Vn2T02eU2=l3Jv20<1#72$s2XT2W]2@{3A?2DF3NC3s61y&3v<2s%0N2JL2&Y2PA2>{2]s3bL27V2f63bZ2up2Ya1(m1{k3oU2oV2>x2dZ27)2WG2&I2,}1c/2TI2zh2#O1nb29A2RM1U:3963bs2vf3Sk1e*2g'1&@2SV19m3v/3Cq2}M1,;11^1q!00y2,Z1>O2E@2f02T+1B$1XR1Ty1+I00z1Ib2h&2<20[]2Ru1p!3E92[i3Y}3H'1sP1pe1xm3Zu38h10{2pI2{$2iP2#+2P{12v3B60[Z2Pm1yG2?J1}K28e2[n1z,1Tw1l?2D81*<2V<1dP1/71qN1?}2O!2Om2aV2%:2'A2W,2xM21P1r{2w[2kR1{p1P33k;1]X1}C3H$3O63Fg0>x1%>2nf1(n1hi00/2AA1ul2sF3E$3ry2Za3NV3543mx2=C1/]2+W1bg2ip1JY1791+N1t'2041&D23W2R'2gj2$q23E2AM27'2w'1cX2b[2uU1'Z2G:1=)25v1/[2F%3M33Vb00+1o11>M1Fs2?A1%82,w2Zj3F=3231G%1'^2YB3T^2gL29>1;N2H*1eY31?2JF1N61l:1]?1+f0'02!o1=a2)f1nm3J}2sy1]H2T[1?q1]t1;{3t!2Uc0,q0>$1dX3e!1Q'28R2O32F32>b3g=2fY1dE2}J1gg1>r2ZA2b31;>1Kk2Ye1ZC2=,3XZ27h24*2v#1X}1O@2Ew2d72P131T35a1bh2+x35N2Sm2^W22;2%w3UB2qu2+=1(A1pT36F2xo2kl1$;3F)1/x1a31$x2aZ26x1O71M?2ln1$U1B;1MJ1GZ2y81Ie2LQ2O@39&1+v1V!2zM1m!2A&2]q37l1{01::30E3g71:?1uE2i23N$1HW2ig2z03Mc2{'3H)1(+35C19w38!2R93d@2zz2$b1ge21!%3KO3g]1>21$S35;26q1ST3cB1k73Q]2cH1TS3643a%0!g32=2Tn1)02K?1L^20x[2c$2O61!t1mL2eP1)X1G;2Ba22WD18@13m1?T1Ub1lZ2V;3HW1uR13;2+)3Xd1Y12SY2X*2rY2G$34u2{w1/l3Fo1r01'd1*h1o/3iS26#2S22es2kx2[x1^C2,J1R32Mu1yg2!t1bN2Ir2Gs1*Y2ED2L52p:1%{1$=3E[22[1J[1[!3JC1;b1n10{)1R{1D>1LF1'n2yh37b1o%2U;1#]2YU3OY2(T2%a1[v1RX3H@15A2?12Z!1Y21wV2[z32w3D}34F1QW3D/2Xh1@Z2^V1&{2>z3l/1Ey3NR21M2{l24h1!323N2Ei2cw2Sy1)E1cW1,G2w<2:T27Z2tR2(W28O2jF2on2Z'3kp2*O3Kp0,d2c42*73TC3cj2Eb1[d2g01Vp1((1Lv29,2T!14l2DH2G52L02Bw2J>2131T]1(}1O*36Q1X13Y%3732s(0rP3BQ1#M2o!1kO3L/25/19@2Xx1H,3DP0&H1+A1K335@2+n1lt2Cy3Mn1<11]E2Yp14;1=w38R1u)1/;2JJ26r2y72/k2&^2P?36c2np1sm3Gq2zy2N+39=1sL2ir1>=1}W2yQ2KL2ih2aA34U3jt3j&12O1>92>h3Hv3ih1kL2GM2M@2pf2uj34N0[[2QA1*F3SO0%n1;90w+1)92TQ20k2[L1qo1hI13{2/42@j1=g1/u2E82=}1kE26W1@?2U?3I@3v]2AQ1x]1+F3C+1)y2H]3wd2mJ2(=29Q3Yn3Og2KF3Ef2+61*H1gU1zi2OV2)H3K10wr2&G1^Z2v&2k/2cq2WN1f83531K!1c]39n2572&)2Qh2dx1u]2Rx2:C1{^1*i1tF1Nk2kO2F71*}3Bt3Mt2bY2ld1ty1qI0!(1IN2Uy2EH2N?0'q0'21l+20+3p;0$I1;D34R3Dj1!E3Z!1c)3bP2a90;k0,W1[+1yq0?h1Ix2VL34+2x511<<1yY3TN2cf1wd2Oj3W]1{/1]e1V%3EV1$J1?Q1:11%$1{W1x:2]B1o&1vB3H&1J21(R2l61{M23l1)Y2uR2#'1^F2BD1Vg2nJ27j10)2sw0@;2Dt1'T2f82CD1Lg2l70}31=Q1lY1ql1}t1K+1YE2%S3St2RG1ix1x*0%/0u)37P1zK34D016'1/q1n=1*+1)I2Hm1>/1K)2Jp2)!2+c1a!23o1vn1T>1nD1I'1oj27M1@;1id3Ub3be1$c1+e2M61oC1qt1uv19F1t91%A1O11dz2)y1v91f'2FX2}>1=J20O2D[2Yi2%j1;+2&Q1b;1!,2B,2+:1ZO3s21<^17{2aq1Q+0[v1?03P:0&{2Ra2''2LR2d&2b13C=2bo2K/2f+2mY2dR3RB2t/1vy2ah3Kt2*H2jV2n:2O[3X&2Wm2%P2Z31?E0}!1f=36E1&,1&b3A61:R2C#22v2kB2vD2d+3X01,T2nm2MX2FW2T'1:{3S$2BS1q)2i12%<3A%2,F2A+2J]3B02}R2WM1eZ2QJ2li1Pr2l92Q61iz3Fi2$}1(G2Ha3ac1{V14*10Z2061d032d3i52$T2W60%Z1=N2yB28;1F[1Mw19n1sX3l13Ar3Vj1>e1Ww2Dr1%B1=W2aP2UV1+O2J#2)x2!j2aR2Le2=s1!h1:80;u1W;2YQ3Cv2ub2,L36@2,/2;G1y>1Bg2Nn1iZ1Ah1dy16S0;)1wk2zx16j1+72G]1{328^3Hp1b#2Jl0:=1]611h2u<1jM1b:1aP28q1lL14V2nF11&1c=19f1Zj1gp39H3N^1%<2A^2*v3Bb1:;1e51z#2!H22@3I,3Hx1Ts1p?1bH2H>1p<1U51f31qm2g)2r:2tc22W24[1ei1b}2/@27%3f}2yw1BT2(12PL1qM2b827{1;E37q2E21x=1/n2O91zp2:J2pd3bQ1i62zG2R81&B2[=2;&1RH27p3Zc3d<2*l2oF1a&1U$2Z[1oh15I1#,1$<3kH2[719h1[C1#(10w2112kD34>19Y1Wq1bA1v<1hU1dH2s<2ss3M20zb2P=3KI32L1tu2}T24A1'b2=O00=29L1Rs3hD3N/3k%1Dn2,=3Hg1]@34o1jX1U71jT2sl1lh2fl2/N0(32rL1Q^1Ql1[)32V2^22N@3HU21B3Vf1jg2!f2P51an2Y$1Yu18a0:w2c721h2SU2:Y2Yy2t$2871Z)1A12Pi3Ks3MS2S11#T3H*2j;1[V3B^24(1mb3F,1vS38b3DB1n912?i1q>24P3aA1$W2}h2ok3DZ3P;3iQ1[A3om0,^2k12X,2nN2Lp2RD33{2jv2%62y01IV2PE1=L2fZ2Cj2l:2zl2ve2q'10A2o=2d02i%3Go3im1=E2@[1KS3812Yt24a2!U3Fk21u2^,1P1f&1%]2Ho19R11t1@m2Z$2MI1w71UC1tT1*P1XB1X*29y28Z2JK2Py1a?2No1zb17^1G/1Oy1z419&2or0&+1,)0}s2'D2LJ2eF1K$3Be3Te2&=22@&3Am2c@1;V1NA2Fr0}d2qZ11gu1go1<>2Zi1$02L[0/z1/%1:G2YH2M31i02YI3hG2]x2?E20}2351/s1#+2d52Vv2nn3p33NY2{e2tK2b@1J:28l0:30)f26i2D>2pV0%a28/2FN1aa33!1}<2161>v2qH1Pt2V*1>j0*!1bR1kV3Pe1G,1*q2C&1qi11{;2TJ1i/1sY2Y{31%23e1&M2&+1'W1'/2{W2A%2&L16(3r{2Lg2aI2W$2U91#v2*}2h/2Gg1*Q1Ym1!v1af21!2y321*3X]20o26h1)d1@a2I81Ph2rB2vd1^:2k[25%2yG11Q2eH33z1mv15'2eX1SH1^J2<01>w1}(2!g37O2rg2j#2nX2[M2yk0';2&!2#u1oH1Us1vG1S%2q+1Pg1t>1lu2q,1(N1GA1Zq1;r1XC1Mc2@c37]1s$3fT1sc1$y2fy1y91CR1I#36J1^l1&01wa1kc1)T1N}00!1RN2KV0)*1Gr1Y]14K2wo31*2=]1bk12({2Q]2HE2'U1%42:k2Oa2#K2Dz2n+1db3R>2pn35'1Zm2Hq2Gf3Ez2ni2kw1,v25=28s2{51[D2@Q1!u1PJ2>q2h42$&1UK1Q@2Wn3Bd2CV3fK3LB0[a29a0&L1tL1'62YG2f51oS1%(1vm2RW1{J0[e1Z'2py0,]1]f38L2$W0{[3ck2#z1w41NG19S2ZU3Ur2vj3n02!s29p1)%2^r2Gq2c]3Yh2j'2@P1&41Oz0[j1m31eG1UJ1m11N12l$1;$1H22N32AN0!z0s=1dj3ig1dx2[{2S}2Ec2c52Ig1$D27^3sI1('1u{1xJ1n$1!W2J21N>1X@1,+1/U1g)1zA28u1+V0[f2Dl2952B{27!2se2E31@&1Hk2k81j*2Qe39B2MV3RT1QU0]e3Gb0($3mM1OH1/a1dN0s'2Y@2xD1Q{1T01^]1Od1r=1,V1?C2Nz1gf23!2S?2fC28a1j>2o#2Xg2PC1,43F/1Bh3RX37L2E>1}E36Y2p*1%F3pq3Ao2M}1o83B@2Dw1Ca1)<2UT1=b18B3gF2x<0]K2so1>40)j1Ha2wz1MC3CQ2$a2#S0&q2B(3Ai1jl1uC0qm1QV21)0kR3gM3gY15B38{2om2C*2#:2543BX1Rv1>L2*h2Sw2YX1zx2iO1fS1ex2Xe2*I1<)1#b1hD1;s1&?2?a1a71yw1$P2OX1zy2Xv1Uo1yy1[g2PR1T!2Ms2!w2#y26;0$X1:43Rq0#]1l[1Dq1P$2):2k^2{12OF2=p2F'0=Q1$d3A037e2vc2^z2{73fn2L<0;^1^,1x72fQ1!j1a{2,)1KK24;2h71mA3UQ2Ze0^J1ry2SE2fA2vZ1n@2%/3d/3iW3G41zr1,22+d2lG19q1L<2V13o22g#2}i1;Q3D61vT2yp0?%1q42}S2kK1Y{3Q}20N2NA2xT3GP2PV2'S33(2i+2h(1w{20L17Q1VT1&y3pU2R50!s1($1GI2ky2n%2Sg0;/1%O2fG2Ke1Jn2!;3A@1Z21&52=f2#P28A2l+1Gb15k2E!2OZ2gP22C1ZD1L?36%2y;1Gs29g24?2ps2a02IE3Yx2pv2@y1B]3dM2V/25T2l,1cJ2n(2hT2Uj2s&3I!3n31j71,l32&2F[1w:1[:1!>3HX1jA1I+2+a0v%2Qj35I2AX0[<1^x2qN2*Z2Q&11{y2Gv2W(0oN3Dg2Bf1j627K2gh11z1x,2I'27c3A51FS1^11qb11V3Hi1SI1H]1:E1TO2St1iX2hV2s/1,U3J@2:L22ZL2hA0*W1Ue23Zf2kW2oj2b=2EE3u*26!1Bm0f51Kx2iw00?2V]3A$31^2V%1jm1&k1qx1}G2JY1HC2)%1Nq1s<3Q$1*,00@23G2+*12h35n14{2HX1uH2bp1U,2i(1>,1j%1Yi2m}2S<2mf22)1[(1xp1#e1X)2'H0!V21=t0+}3Zk3S}2!61)u1:51r!2Yz1YR1na2:%0!]1:V2oR2Hx1*T1*x1/i38Z3XA1YN3L33l#0xz2E&3Uk31}1:r3Oo2Q82g!12c;3k22l=1}S1;?22H20g1b31'A3Ua3kF2hl2,(2=$2/%1f,0)>2UQ2/Y1xA1o)14H1F@2?%2<;2}x1zM2:n2lR2K]20^1J'25?2hM2D11bB1S?29j2MP1^'28W2bm1i'2[936z1yL2Gd2e?1V]29%1n631!3c,0!i0&=38z2VW1mG1{b0$D3Cz1sM1qC2Sp2lI1d{3Bx20s2PP2Jj0{U2$^1Ob1{z2Uu1)j1xH1NN31{0%G1wE2H:2)W1%w2202{^1Pv1;a10p1q?28r2&{1(62IL2)j1h$35h1#E25}2Np1/41f(1,(11/2qg2Lz1mO2f=1gr3sG1gD2GB2c}3601r^1cK20*1$73k!0;U3jr2R41TV2A[2qe2[+3G:38M1+[2yy2q039b2[y3Y@0>P1%K2uz1Nl33J3T]2tj2y+1Ae1712#Q3Qo1*%1t+2B21;02IS2jH2Da2EY1Va15z0=#2Y=2z]1NO3341uM2{L1C12Eo2Uv1[%3KG2L%2^#3V(2[@31&1o[2H(3Lg3CV3JE2:,21?1;g1)G2K02f30=834i0,[3pL2If26*2OO2OE1G*1Y)22n37?28I2N21Vq2[N2<80wy3Gl3G22>I25m3Fs2TX1Nj1vz2Hl1cB2yY24&3Zo28V1Y#1=11r51a=2iW22F=2LZ2qM2rK1HD1'R2Q}1HU1#>24C24'2L!1Bn2yW2CW0?f2Z{2PD0$12r&0,?2U&1rB23R29{1O}2sB1BG1ll1=V1@J3Mv2h!10t2L*2c61a41ab2d]39:1Ft1(h1yI23p3O<1+T1:g1lF1^X1zt1qF1v/1>K0/Z0,B1(C34<1h+1a/2v!1kW21q2Jm2Jq2^92BH1=63Xc1mB2y52DC1HR2&V1ij1aI1#<2W+2Lo3Z92[E22j1f<1%M0xQ1+&1#=1*=2Rh2lm30{2Y02HP2O>38k34T1bM0:{1ko1!k1wF2%X2(m2?l1(O21}3Lr1n21#c1gS1ar2tN1[a0uL2Dm1r;26z1ZK1&)1{@1>s16V1xS1{d2fK1e(3n!1TC2sU25;1d21'M1rg17G11$1?!0ld2MM2r02Y,1!{2M43F*28<3Py1t713g1>I32W1xU2NX2o@34(2ov1]a2CX1d+2wd2d91y03Lp2Ds2Kp2F(2M/1xw2ZI1vN1D)35#2>R1zT25U1&^1om1rj1;P1^}1?%3F@1&C24K2{Z2u42gr1[E0?n1Yg1Wx1+n1Ai1M12}b2cb2mD1!'2TS1w#1{&11m1Nx1fk3IE2CP15W1WN27L2wK3tH33e2/T1M}0#{2)&1KX1x50(z1nE1jE2M%2w!2m:3GB33N20G1?c1;R3sk1qe3Lm28&2;=1mR2,n3b^2d[2(B3h}1A31]'1hO2X@1}r2Rr1/c2Q%2g;1g=1#@1/?2C03d$3J>3PC2h82^L3Ho1{v25n1)C1RI0xK1Q}2hI2XU1OZ1%12U#0%b1Co2ya1x<2B=1L)24n1I[3Xo2Ui13/1z^1!}2+O27]2B+1]c2$j3Yv1X,1rt2a61w=2>s2Jd2R+2C[2g31zQ2Lh3gA0!d2/*0]R1(,1RK2LG39;3N61}+2$72=b1k@1L&2^D2G721p1I22l81jv0)^2%R2bk1=X1xd2)b1>f2:31w+2>p2ZO2>K21T0'w1@}22S1:Y20M1ta2W01vI2vb1jQ2>l2mZ3k#39*2O<2Bv36M0yg2rR1'J1g{1'@1?m2q/2#W3bD3Cy36628v2^82Cp1eO1yX2,^2361%d1R<26N1R71+w3KE26:2Y%1:O1[#2XH0%92B/2>(1@@2}O1w;2:?2WQ3b418+1,p2cC2dK1wT1%U2%#1m:2JU1C)2Pn1qH3O41T}1J*35r1g}0+,0$$3AK0^?22!1,}23]0>(2;729e1y61k,2f@1:m2'91,N2FO2u31I]1#31q*1,u1i(0(!1@81,y1;Z2+23Y+18u1'l1uQ1KG1Qz2&$20H1(M1!b0$u0>f2K&1}p1he2gt2Ua1le2?U2K71l]29i0<41@f28,3W)2i50{o1fu1zc34'3VR0#S1tM1A422{1;p2^K3BE1nI1m@1u02>}2oN2V[2}F1$M0@J1Tb2c)3D02VB1%p3Ei1M(2LO1ZI2LY1jB1N^20&2bZ2qr0@h1f)1kR1QL1r$10c1'F0>b1K=2mp1!X32o1S50=&1rh22P1Cm1th1+G1lw0?i1Z@3R21xV0,F13&1W529K2=%3SD2bM1W]2hW3Z'3Lv1Op1*v1c61AE1m{1K'1m[1hJ1VV2,$2Ud2MD2r;2ed1Yc1(t1Fl1J40#)1]P1$g2%d2HJ1132H<1&:2P40{C1GY1k'2nW2?Q022$1r:1[W2)73cO1^73Ta1FT1g+2{x2,438r2d22EB1I>2*!1+%1l=1oB1oZ2V:1@>1EH1MQ1JH2a71yo3M;2;a0z,1v[1&114b1x?3Ip1'32SP1N+1pi1e11}P2IZ2CE1Zd1cL1!a2'[1AT2Q32Mj3w=1R^1R83XI2*<1y*1yh1Wv3HI2I>2{E3Uo1u*3ta0}J1J}1!(0*R1JZ1Z{1H@1ch1[m32A3fr1qW0>{2'p1fF1qq2/W1*y2;J36(2vw2M^1Og2n311e1pG00^21iV1b(2EI2cx1@j2qk1H)2*u2@k1D^2z71UW3Ov1q/26V0,N1iL2pw2d{1zD3cb00&26%1Hr1W%1XZ2&A1?n2Q{1GL2UJ1f?1$L2vL24/33O1%m2R)2CK33$2Ny3S238O39j3N11i72!51{o2,y1ok1Qw1LU2OH1mz1mC1xE0%15$1!e2#4129u1]p2%72zp3IB25F2#;21R3hB1dQ1oU1CU1s)1bL0ry2x01Dl0[z1Zk0c^3rS1qr1BH1UD39@1Dm3V]2Go31C3O32@13uq0$T1[t2Os3cK2xg1I?20p1A'2U72(/1zB24<1w11=c2cI3G>2Wo2Kz0;Q24b11A1[}21d1A+1fA1Bu1vW0(21]730^0;41L]16=1CG3ng0'D2Ku2+;1==2Ur3AI1x}2N(29/3S%27,2#$24#1O,1uS16s1;22>d1Qr0?w2p$0^g32}0#F3Qb28N13n1l}1/K1G!1Jt3U52t11&>28n3WU1Jq36r2m31K;1Q!1i+2VH15!3K72a=29o27H1@K1+b2pu2qQ0!e1912hv1oK2Lx1Yr2j22c>2mG2Jz2wD2Yg0':1(:2Mc1A,2=@1hj19b1F32hr2Af2D32H01T)1>?1rx24L2wc0]]2Ot2#@3rJ2ce2os2v32pX14M2xc1&<15g2@v1=u2m81^43DF2Or2?,1iq3PK2US1@k2?K2Ev2j)3f=2}c3l:12u27N1Y03j90xv2+Z2$v35Y1kT34}1Eg1AV2g61jK2$V2<1UE1<61@32+03D%1,r0+L0%U1(o1=o12F19W1/T2sp0tz1+y3R$1lm23g1k*1XF2s!1je25{2Fg3W73nx1tW30I1s*2W30}L13p1u+1aw3Oy2X81/O1='24z1?W2i^1u^1tN1Qv1]527&3L%1S[2jQ3Hf25y2=R2gF1x22H,1$s1wH1ya2mr33:1]o1oe1+62F!0%x1]R1SM0)#31Z2f>2vi0$z1gG22m1SA0,V2FE12wx1jD01bb2Wy3RW3ZS2U63EZ1D61O>1sd1ue1K10$k1r@1^f1wM1iC2,P2m/1c;1G^1Tu1u!0>t2*/1Lk1jb1kd1Z70]81(L1r,2q52PS2!u3QK3j,0/K1Sx2kZ1D[2dQ1t)1[01pL1w*1bm1y(1gR1Eq0iS3Uh0=Z2yx2e+21K1Rl2/62vh1nK2w;3BR2460:d3rt2%W2LE0%}3GH1{1ks0qw1i;2382eL2s^2hu1;v3Uq21a2jX2y92[V0n(0le3ZB2id38t2tp2v02770mC1H#1kw3fR0g)0%Q0{h2KJ1pJ2(<2!d3j=2IY2D;1gv2L&2X#1EZ1400&$1;'22@02fV2kj1Y*24t1(E0tQ2GO1YC2PJ1:^0%j0:@2W83Da30@2F;36A2Wh1z@2ER37a1F;2w,1;:1d&1Y(1:!1S(0tl1*z1Tc1$v0}W13U1k61cl3O>2;k1UQ3Wu2sT2x/1El2?Y2y@2Sh1Ik3P21P11U32pD2s*30<18]2j$0XQ1D&2X00$s1zv2E/1sj1#V2bN0!82+p1}w0}n2uy2&t2L,0$e0yz0[P1t!2io1L+13*0:Z1E}1E;1A^1t#2kN10x0*X1G01of1Gu2jK1Kn3AX0{523m1sl2cP2HU2i92E}1ss2O]2i$3UI1l>1Hl1}b1jo1ms37k3i70{z3k,1[S1>!2K{0@71$A1Sq1B@2ry2U21D71]V2z[1EK24)2:U2&:11K13N1>;1aQ3Y,3G#1WT2dV21;2%k2%!2zT2l!0*F0:52}B1[51F&1eu1t814[3CS2[b1>R2gc1Wr2Po3EC12}2U[1{g1e63FL1YY1aL20v34H1F01ik1GW0'A0,R2g:1AB1sZ1I61G11nu3JK2@,13e1%o1VJ2dB0&f1g>0mr10C2^i1se1Zs3op1h%1=F1)S2GD1m$1,h2NH3Vs1{(36{2S1]x1z93PJ1g,31L2mi2b(2y22zC2mt24:2o,1xQ29E2jh3ms2z13YO17/21x0/e2}/0{]2I00e:2'71(Q16;1sV1px3Y<14N1e=0[%3e&0:B1i42413D+2Y]1G+1'y2;130&2#52}'1hs1V71P/0xR2!82r92vr2br3ja3BC2=92sH2W115=1q+2Sj2fT0k&1Gv3D!19!2g&29F25f1$'2SQ1/b2I$1lv2W#2lp112323u2yM1+,0,j26928(1AR1Xr2eZ2B@2zN2K!3JL2sz1us1zq3e52I^2Xw1Fm1(F1SW1df2z@1mx1Im1xl0;i1I01JF14%1141N[0&92xN2c?2#03WB2sn2^T00)1?G2]62&0t&3mu2t82P!39Z17,27+0we2IG2Rs1?^1&E3QU1,{1Z*0#N119k3RI2'(3If0s*1Z12Ft0$f1;q2E:2W52v91/033021=1gE20a2dW2hd23T17p16T0:a1Zy2kL2Li1ps13O01r825#1CV1f024I2?V1iN1GQ2AR2n83DV2H&1PX38F3B)3d=1v+1x/1&L3p^2}43C00i=1T51A51H$1Yo1)]1'v1g71aC3RF0eq0q20&C1cS1lq3uM2eu1cg2,s2L{2rM1g%3652Pj3Ew16D37c2}Y2B?1{42b42dr3KS14m2[s33&22f13b1[i2F{1v;2R;1k23P*1WU2*Y1i]2KC1={2ib2UI2=+1p31n?33#1^S2y/0vK0=1R$0[Q1)L0)G2$$2sS1*n1/:1*^0vT2vF2:916o14c2Ek1kJ3q?2KS1=M25s2HH2Bn2i{0:p1+j2rT1!S2V(2Y?2[e28M1Ja27r1$+0xa12j1v22kc2e93ga1Mq2er2Yj2en1(B1(11Pb0>I0/*2L11:32iC2h91Ap1tE1}&2FS0%<1s02B;2i:0u[1T40$L1Gp27=3q>2,*1W41qs1o?1Gx1S71*U2LT1s[2E)1E)2R@0)12F01'L1*?1)?2p+1p91'o16k1D?0}N3Mi1tP1$z1#60'h11i1L51'G1i<1Z#3X;3C(1;,1X81fZ1fB1jN2h+0*x3Ca2782zA0x]11J1aF11>2Xs30j10d1*X0^s1%;2w{1zI1''2Yu1tb0]V0=W1IQ2;T1=?2>k10{w29v1y[1CE2VJ2tP2y?2R%1d}1O80[40&}1qJ0()1=#2e$2Bi2+K2%>2u?12m2i&0>i0ji0}917q27q23C0oL1{D2Y22}]0+32o*1r<0*80?E0]S1510$;0:x2Kc1)v1ky1a'2/01pD1@Y2fi1Rm20B0<=2];2AP2Lj1'02jR23q2w22QD3R11P=2Mw2aW1Fu2/J1^91^*1/)2/K2I!29f2re1yV2aw2H+2az1)O0?J1*G1,%3L10}x1[w1:#1%%1)i1V40z%3AL3dY1F]1]r0>z1()2C{2cQ2C12B}0;[1Xs2[B1&F1dR1$@1o92YV3Io1mr2Wa1n^3AE1aq1i>12+23a2n$2zH0,O0f!0!^16M2(q2i'1GM2N>0&72m^2w?2}H22?1zO2WE1[816^2s31Cf15N1D81Vd12<2&i2&61]g1T;1Vl10y1hK1(q2eE0]<1Ep1%+1rL2Qk30O3ai1rR1^D1j=0)l2BE1I$34d2e@1Md2F&1#*2%z1>k1ng1J80>,1YH2KG2H!1r)1{r2TD1%?1/X2t41xI1:T1@e12T1[,1}22FQ1u11(r1,z0$%0)/0n)2DR1b<1'z2L}2&011E0,81Os2q?14h0+B1Ws0}k1!61dZ0@M21D1s51x41c?0%$1640;C1y+0;+1K{19x2sc2;:1Ih1#:0&l38J2fM2QQ1P&1lc1{f32*1mc3M11'k2)t3Q332u2e{1[>2G?1&N0!O2Mn1:02qL16@2&w3H]2W:2e031W1W*2rq1Ru2gn1bE29C1!s2v+2EN1,C00[2J'0a$2a>1{L2Mr2P#1lX1*32E=0l/30422k1pO3N32vI2eY1u828L2)P1z(2dT1jH3OS3AO2ns2:w1802al1/>34#1n'2yO1T?2[v3F?2?^2X+2dk2J;2$?1I&2A91rz1663q!1or2hY1pS1LG1Dj1Tv1PI1hC1TW2xu2EF1WE35&0,t1f[1l92S'1l52?T0t{0>814z2S#2na1{i1fK2!S1XP1AC0[M2xS2{U1@62'?1BZ3Q83K%1W(1(&1C@2j52+v0^h0]u1zw12%2ZQ2yq26Q2ct1F:1',0/a0>N0(e1172RT12e1p#1{N1Rf2+u21016B1]I0:91Lw1zN1%)1TZ1=r1#Y0?H1m]0'/0vP2L'16N0t?11l3Vl2,73Ju1y;1F81gx0zN3mV2>T2VA3o82rX0$M0/f2+(1a^1;50lB2;H0!l0$o1mi1zV2k&2Xm2NJ1:l1R>2(@2Ix2lx15D00]2gk1(41Fy2/z1o'0{R2jz2741r91K,2E(1/'29[2I:2GF2Q<2O;2?f1mj1^01uj1[11b00+>2?u2Zu1um19#0,k2eO3Aa1No2Y!2M81&c2Ug1$]0#}3Md1rT2Mg1*f29@1E62Kd22o2zr2I22jy2C720q1AI2L^19c2472?y1Jl1,$0;02Ij1#21k:1l12be3t/36]1xX2e42e/1hX2%%2Mk3891'K1cs0wA0@82,<3mG1B*1Y43a31wG0[J1,d2MQ18;1}e1g11Re26[2?42Bd0@A2*F2a,2Iy1ir1}30rp2K13C/2$g1G)34t2r41ic2['1U91E@1{51M'1?D18O2091}n2UW3Du1)J1m}1Nb1b*0'a10<0&U0gH1l<3wH2xd3f(2]h2?/35D2'L2*13Cf3BP0x?1Z}2{C1%C1})2}D2o73J+1Iv0{/1NK0o#1b>3A;0>C2Ce1;G2Rb2Z;0#p1?>1UO2Xb32r2Kv3gT2f:32@1t=3221q50/n2!Y1h91It0y;29J1^R2WO2qV2uB25W30)2zi2hy2;;1Ur1Sb1]<1&w0ns2[S2u%2d'2;33PY0mj2yb1Ba2)[36;3F52kn2p3D{2u,1P>18)1h:2U%1%,2Tm2a;20T29w1H!21^y1**2/B1TT1ho2kf0[H1r'0x+2us1KO1M/2oc2z+3UZ20@0rN0,{2N#11,2g}1wO2{d1+*1ff2BM1O22MN3A#2j{2}k3Rb3t)1=D1qP0}p1YO1ke0vQ2/c1v$0;F1KL2Vl1k/0uw2G01%I1a81!q2Fb1@L3hm2)a1b11oD2AE2pT2>W1gO2@M2J83U+2Un2Lt2Na1,#1QQ27x2le23O1d!2=U1q;1kb20F1Jb3FJ0>o1[u1Z+1720/#0['2$n2t<2me1cC1[]1O42e}2dD0/A1yN28w2S+1MF1b{0o512^2#n33G2oo2=F10o1+;2q62Nv1bF29U3IQ0%M2Tj1b?2f'1Tp1Pj3A'2[X1XV1?J3NS26,18124f1LV1!421918P1B<0!Z0>a17<2V21<:2K%0,@2sC21H0us1T131z31$00>1I@1Wn2HO1f*1Mg0#?2}j23V2u]1c22Rd1sa1UR1f>1gX1S/1E+1GB18#0$U1nW1OI1g^0u;1['1Ze17:1MD2KM1t21ht2(r1R?1p41s%1Z!1:913G1@U0ui3m<1Mn2/31TN0:y1(X1xe1m*1i$1m41b'2eb2BI0}(3Sm2DV1tk1yc1vX1lH1x!1r10[83Oj1n[2^Q3jk2S31]*1iT1xR1^P1Jp0j70xr0%?3Ff1q'2}$11)0>'3hM0%w1MZ0p?0vv2m$1Il1;B1^c12bV20=1sR2@;2i]1)H35J1kr2oE2iQ2UY2AS2=q3Fh1Ct2U32eS1V/2ZF1xs1nQ2/f1$/1k82zg2;v2O%0*c2Rw3hp2,]3ey2iM1l(14!1?s0XL39c20d1?Y2SW2Ve2^n1oi1!?0%+2d63E}3Q61pA1;^1h@3F!0,L0]W0#(2?P0]b0r*1ML2WU2IQ1!)2RP1t/2y^1@2(J1T31bi0^w3Ku1oy1sT3Xb1ip1g<0n+1HJ1FM1KE2Gz1A(3ZR1@$0}D1?l2AL1QN0^91Z31WI2512ey2wX21G0!a2Q@1ao2,f1ZS0/!1gP3Ts2K[0z71Xb3fu1F{1eM2F42NB1S;1!@2O*2b61uY0:?2a42IX1uT1D]1zf0+/12Z1>H0k#1MM0*Y1&]23j1T[2;I2)d17g2+J1H62nV10@29M2Pe2J:1'=1?+2%h1e711:2q>1Ma2zo0vl1;c1&t2/^3n>13cC3e$3cf2i71ES1c00?N2K81;k1(g2;X1pj21N1RY0{^2tE0{j1${2ai2(l3nn1=z1D40xA1@00rX1I,2{q1980lY2C@0)U25R2sJ0#21nk1:I1322tX2}l2$z1hg1Dc3K20*z1S+1uU1md22y35,1PL0?g1@S1zs37p2ql2tz2/+1gI1,?1sF1du3AZ3J82K)2OS38D1JM1Aw2e!36X1CI2Px2s@1;42c<2q=2uS28=2vl0}+21>2H'3v#1f#1b)0m:0wj1L*11?0!x2B60s]2s}2xP1M,11[1f:1L30=o0?82Cb2Iq00:26c2FD1c{1Iu2k3Dn2a}2Jr19/0r=1^p1Mo2E<3G(1Ii1mH1Ia2I{0tt2QC1TK1}41Np2!$1QK12G0@{1vA0w:1j41!w1^k21^1){1>^20!1)x2km2Nx2/M2Vm1sJ3Q&0z#0[A0&w2t:2Wk0i!2CU1JI2W92Hc2rn2T#0{+1>(1o(2^F0]s2T*1Lb16P1ug0$/1Qq2bG1q@0h=1tq2sM1{T0+z2l)1&r1DJ1UF1CY1(I27v2*&2Xn1=!17)1Si1+c1U'1QE0;y2',0oS2)i1,Z1:t1)M2?H1j#2>?2D]1s92a83fw1(V2WC1v%1!Q1,723*1'H1OK3UP3RS3Wb2+>1ti21b2(+25M2722QE1>)2+t1aJ0;D33Z24T1>A2ID1fH1;739t14x1tc1@215a2dX20P0&x0fV1;z18d1Ko1Ga2vV2JS1hp1+42Az1Jd23)2D412w3S(0$*1!d2qs0>A0#Z0lt2Iu1ZA2X)1yB1J]1](2^S1zm2zj1'Y1k^1q=1!02Z50%I2^y37%2no2ZC1]d18<00'1+{2P]1%[1e<2pR2y[3Js0,P2Oh2#l2k02D'1?r1eA1tQ3Jq1V;1gL13t2=L2Di1w'1=l2H91'N2c/2de0:j2Yc39}1re26p1^G1PV31y3Q52q41rZ2FK2$:2WV2BQ1R*0>l1#X1kX2lD21&1}$3d;2%}32#1Kv1Wa2G90(J12S2G32eW0)[1+!28X1D{2(U1mP2xy1u;1t;3Gd3w22x31;O2*%1iB1VD19:2KN39f2TT37+1]j2a]3br1d90<<1vO2p12cV30D1+g2y12hn1h&3HZ0+;1Lh0+Y1M72WI2&z0&r21+1IG1[[0{11?&1zz3mR2SB3W'1#A3sH1xa1bv1yC2gd2@^1&%1OP2]L3Bg2F81(z1d32))0*?3M(0'P1Cl1/r2Aj0]40s,2o}2r[1c%1Nt2FA1q02Lb1k!2:72#H1!+1l72kr1e{1uV2bi1^d2Dq1;I1;60{:2YC0x11eK2N;2JO2Yk3h52C>2SS3G&2C32&,2LD1$O3tr39Y17F1*j1N:2Wg1nH0[B3Ah2Vz18r0+T1N51h]1':2)K1I*0[Y2/V1&U1h*1x)1=41'+22a24J2hi3HP2*U1jS2Qb2?S2+N18x0vs1rF1Lr0#&1cj1IY1%V1$,21j1'f28o2p]1FF3Li0/G2u/2Ul2oO2Cr2@S3Dx1:%2Rj2^(1@g2oi2j?1=G1J/2LN2/v2700>T1,Q2si3B*26j2y>2^{30!2='3D^1{#1/d2R11hr3262iZ16H1Xi0z^2c^2S51i53Nc1T*2[#2C62Ed1y31#t2gx1A[3b51}M2k61'[1cv17K1I=1031mo1V92Q;1W/0%>11a2bw2052XR2?]1V*1^V1ux1:N0?B0^P27e0vL1Fg0in1/52'o2&n1L,2712@p1$F2D{2ti2Du1Q<1[=1/}0^K2jG19K1hG3351nv2R21IJ3922k]1'72172Xi34!1sW2mN2ji0&a25B17x1a<18[1CX2oM0pP2Av2Ow2!}3H31u33FG1A]1b$2#A1bW0}l27g1WF1^52sL2)o2j81ds3aj2E118j1P}1wW14L1o#1;]2#?3Y>3Z21V61i81,83Az2Bp1Yz1)*2o61Hi0mY0r]1nw0nj1ci1au2DD1QA2UH1TQ0(D23:1W017t1)P1nF2Jo1[^2,i1Aj19'1BE1*51/h2+31me20]1bT2$r2w&0UT1TA2rS0^Q1*:2eR3Nx1Vt1ov11;2X:1?K1z$2*+2000jX19o2FY1@z1dB0s00zm2FC2{O0nm1gV21m2Zy1OD2R[2(I2&p25S1K03Un1,F1;M2zn1V(1Kl2XL2mU2p%2Pk2u#2=I2;B1]z2q&1un1hw0Vm2UC1'<21y3JM1,11O%1Z02ys14Z0<82Nd2uV1sS1]y1y41)m2g/1sO2B$2UR1],2Ib1+12GP2m500,2(D3Gu3H?2w51)Q1Lm1WG16I0vi2eC3NZ1z=1S^2uF1R/2V{1C%2YF1&[0(;1&G1E&24g1?Z0=?1z[2hb2OJ1js16l2JB1X21ru2M{2NZ2,{29]2BZ27l1F?3lG37Y2d,0^$1dC2EG3e]1N02'I2K,2522P}2y*0mN27@3C52MZ1?61!F2Gn1BM2!e1ro2td1L61^i21r0^A0n=0v90uE0!)0qk0(j0xG1eH1xj3Mp0({3R[1?<2Qy1{,1@H1is1tO0;m2k91dF1Z41F!1S{1]/2Bj1Mi2@V2WP1ja1TE25X19d0;j0>w0[#1q[0@'1K22,g1rk24c1uh1pK2G#2Au1[91za1nj2QI2MJ2SR1}^3eq2Pp1PQ1M01WC2*r2uW1{s2j}2xb1vo0zH1Sr19G0^'1O:0,o1?82Vb1E!1{92@(10G3Na1)>0y<1wq2nK1vV1FD0,e1u?1nq2P(1RJ27C1n(1V23D1X[14/2$)2i}2kT1GR2n,0rA1XS1Vz1%h0{21jZ1sp1b]17z24D22c3pt1,^1O+1Me0hC0b{3M#0S26]25V0}h2p=1iy1ek1CO1v>0;I2nl0vj28)1+P2NR2+Q2)g3951!M1m<1T,1Yp1pv1'#2zs2yJ1Zb1]K0$Q13x0)]2!]0//0!0:22=<2D01FN28'0'I2I]0;&10u1i[26=1)F2]Y3Mq3Z&16E1vr2To1r#1Ak3ZA1sg0;:1]01X52K32()1d,2zY1G?0?30/b0$Y0^q0}F2It1NF1TU0}]2*X15r2^72O52>20)521E2tr0hg2A$2qE1nX18v0@K1qz0$t1p,23h2+h1<23K,2}u0@!1ak2DI1Ci1bl0*I1Kt2I42P21$#1%i1[Q1d?1nB16t1LR2/$1y$2&@2{B2Rm2qJ3Tg1u71iR2Fx13:2O}2l51J$1N]2O+1hl1%#1c10:^1;/1R'2WF3Ap2EO1a)10'1l20@s1Pe2Y}2fe0!n1p71a>3Jg1gh1Ot1>Y3DK38)0&;1Bv2YM1yE16q1&i1>Z0uG0}e1ol12&1cH0h50%q1Y914O1Hs1*60f[1rc1Ab2nw1H;2!22lc2O'1Fo2Gp34C1dI0+g1b41]M1Ay1991:b1#n2IV29l2HI1eg2s'1{U10L1EI2[R1)s3Dw0;q1rp1Km2600tm2#,15G1&$2bP2j60[{1iv2hZ2SH1pm2o&2oI2;S0!/1rq0y!0zI3N729z2cy1{t2^N0tM3B+2V#2J}0]H2HM1e]2xK2UO2K+2h<1{82*x34q2CC1j81+L0{a34*1Sm0%E37J2lq0[E1Gk2l*2Xp3YA0&M1RC16X1'i26w1;F1py1ni1'E1/E2s,1y'2R^18U1W:1Y:29R0kP1Z81/B1JW1k'1j233c0mg17k0]E0'#2K^2nM22M2ZN2?*2e70t;2l43Y00kq1#P20e1sA2OG0?U2Lw1l!1sU29W0up2D=0W>0vZ23L1SJ0'12<>1%62cN0u#0l@3LD0n%1fv1vM1$K1Ro22=1tx0/r38l1]W1OF1%r1X#0101?a2:>2Bc0{x1G62p316*0hs2xp2,D2Dj1!<2We1dm1w339W2Gb1N&2Nb1V>14i1<=1/!0,f0xd10,0{I1Mx10vz0c?0(o0$'1F92)>1VG1KA2Kh2f,0pl2mo1N=1lx1&Z2kX0l=0&p0j{1=&1[20q!0pQ1ZG2y!2Q*1wu31M0xe1oX2*#20b2[G2yr2V&2Vt2Qg2:m2Ry3cg1q$1S20p40=d0#d1ZE1Iy0/[1%Z3i!2:{0*L1Wg18p18L1jf1K51Sj3Fv2{+2I[32y2Km2H?1%e2R#2241&P0ne02WL2as0fm0Yr0Y$0gt0fb0}z0%40Wt1Dx0sl0'$2p42g[3uH3KT0ud0(+0:42VY0lR2E]1RS0wv2zX1mu1K&1751ub2N<1zl3N51$a0q@0uj2Dx0fW1$10;50!q0z827P2E922K2bc2Ux0Xr0?011k1DD0y>0x{2b*2nU0qA0(%0#n1oW2hB2NI0ub0^x0gS0[n0110uy2Fn1$Y0qK0fX0nW1vD33K1D12L826X1{K1XG1*11Zr1C^22E0<%0rE2Qn37o0$F2/(0@00Ty3a:0[^2QX1=K2Z*0M/1yx1?v0tV3ZN1Bx1bK1*o1}s24$1T:0h:1)$2Vp1pW2bq2N*13f20h0tf0kf0Ue1jt0oW0s!1WW0t}1>'0y22[?2+@0sP0%H1;f3U!1*L0t*0n;1Xg0h#18824r2p!26&1SQ11r1'*1S@26Z2bg1$%1Er1<'3Mb0j80bQ0Nt0&X0W}1Hh0v>0uA0kt0l81180WB0m$0ge0/g0=p2'j2*A10X2Xf1BX27G1kC0=N2KO2D+1Ow1:C15#1l%0Z#1u21A21XH1Qj0=41]S1%q1n]1e[1>J0u!2QK2]#1qO2gB2is1J+0rJ0Ze0l,1Ln2/H1W20b>2fU0lS0gr0#e0z=12s0j91jF0yL13C17Y1>t1VH0+?1=x0ue0m2g41oV2881&J0];1TF14s3D)1#B1G#13<0x'2@t2Jf2MU2{}0%d0/S1}R27f10YA0&h0ZZ1fq0g80Vc1ZJ20y2vK2jo0sA2@O0tW1s^17L1uq1em2AC2rE0$G0sh1a92P02YR1{+22l1:J3Ud1Gg1]N2>50@%2m'1%f2AU1EQ1n}0kb1JN1250=@3Ht0!{1#i1Y%0g[13a1SL3Lw2(R0UA2H#2'^1tv2eQ2=&2H{1=,2Hh2&S0yW26{3u80W?1gq3Hz2=?3XT2$>2fL3Gf0p50X@0}R1>B1d731O20x1+r1I%0yu2*'2M02fa2Rn2DL1dd1@43W>1er1j[2OR0qb1,q1hA0pR01324E2tJ0u]1N42y41?P0mG2ap1O93X,1]Q1&(3ee2Mf39y2U11]]2$S2Wd2G[1RP3NA1ed1}g1Xw2/O2ar1MH28*1Qs1u40;61al0TU0d920f1KY20S0e^0mV0mQ3Ky3AA1W12+A1!L2At1f62X'1T<16x2wh1?R14714#1m60xu2V,1Xk2q]2{k28]13h3GX1Ic1Gn0?{0tG0@?2cu2[F1W}0'%1Mf20K1ay2}W2(y0>c2w/2jn0sw0UC0+h0a=19E0fK2nH1pP2j71y%0(?2$y1P,2cm16Q1+m23Z2oY0*r1Cx2(G1V^2Hn2?d1EL28P0$41Rx1u51)!3T<2762+i0*<1d@1YT14P0^21=Y1GG0d1mT23+20yr37S0=j2*30WS0cI0!90^T1Jv1dw1G31IA16>1qT0tN0l21=*0#L0'Q2W{1bu1@%2NF1:S0's3L#2RF2:g1A&1cd0&J2AI1Rk2271kY1YD0j=2EW1gK1i&2g*2Kt1nc1On0l30oo1EW1b/1xc1aM1GC1w[1E'2O#1VO1aN0$90Yn2hK1K?0*412b29x1c}1uF1W70)?2l[2(o0yh0,g2%00[h2!D1Oi23b0=w1A82O,0bK1xz0ze1hM0uk1&Q1/+1R(1hx1Nc1/V3my1OT0:M17m2Nt2qi1@<1i13k(2;n1oA1i}1:*2w)2]R2mO0Mo1U81c'1bw0&<0s?2FM1X=0#P1Fa0^a0^i2}Q2Hb1k&1000oa2[o2H=1;j27m1Gl0=50%J1y!1bC0x42Pf0y/11F2}z1*&2zF1ME1mU0v21HT1190tX1HL0:U1bI1v^2fx1B(25:11+s1*O1n82[K2Rz16W1H42Lr0*O0(}0jb1W?2#<19=1jL0@@1iY0?O1481pz36>2TZ2xC2'<2&]1fE2Hd2>00;g1^m1aO0@30p}0gT0Y+0k%0n}0nn1}]0{;1t427*0<:1Su1S01g02o$2S$22+34j1')1K[2h11Dh2GX0L71cI0OO1Je3TK0++1NB2q!2}n2Ex1Bb2bd1cA2Uz2J[1!V27n1U/27X0y%1QR1qL1Lo1xf26J0[02?C2MK2yn2]*2$82}<2,51r?1CT1W&0=O0rq1f{2Jx1Y<0q03ST0L0%y2Kk1g'22r1xW1=/2;A1NV0^U1p/2Od1)31jq0)60*]1}{1fb1?[18:1C{13$0%k1^s2ca1nL2G@2l{0#G14E1/J13H1{$0>W1wg0Yx23J1BN1!T1RA1F42Bg1b+1,<1Z920Q1gj1lf0%@1mX1oL0:q1{*3JD1Pu15E22x2]70{61l81523Tt1+R0{i2Mo1/0g@3EB2%L0yl0v70*d0*'2C;2kV1;)28b1jc0ot2M(2m;1Bc2cW2Wl1G=17V1z?1LQ1:L1&'1S!1r>1WD0<52X=1?u1G$0+U1W=1Do2FB1no1rs1*{1b,2So1lr2e#0+41#u1K(0y+2tA27D2AW2lP2BJ12M12f220w0m}2Y#0b02Rg1bP0W@0J12c0}a0!T1Qe1EX0:}1Q70<,0'42ak0+m2sZ2622nS20/3Ex1jW0=]2mA0,&0sp0&%0{&2m{26S2f(1Vr17}0:R1Nw2K'2K=1:}0ck1Tm2QT0){0h31j}3Gs0s11[J0gy2do17[3Zl1<&1KI15H0}[1E82M=0#H1Cy1MO1@A1NC17X0+51U+0xn1nV2jC1]h2PQ3421)73Ow3CE1=d1:u0nb1OU18l0'31ku0]C0}@2262@i14<1]T0oj1Q)0u?0=q0h61BV1tl1{H1Qf15S22B21]2ie2!O0yP1$o0}/2%Q2(O24{1S'2:x1#K3X70rS1HE1XL1GJ1?f1@M1n&2n/3FX2DJ1Ul1M@1L72N0[91XM1e31$V1*#2Rl0+Z0%e1U;13R0bF0=(1:s0=F1{=0ql2}71*93Y;2xI2PN1Uj38f2qd39M0{P1Y@1s,1%L1ib2}%1Cz3Cc0#Q0/Y1%x3BL1f92>Y1,J1NE2;W1N#2xe1c+0tS1*!1Dg1,50+w17N1]80'i2v$1Mb1Br1%H1Es1UX1gs0wf1;u1cE1st0pf1?F1ri1Hn0gb1S31?)1xo26b10*0yf1a:1u<2m72Si3A[25$0=z0ib24u0:N2gT26l2ol1tU0lT20z0o92Vg1B:0(,1jO2em0)v1HY1ju2?+23P18k1i?1y)0pA1kv1Hb1}!1j90&N1vp17u1/L0k^0kS0?(0)82wk1K}21w0s<0bn15X0v(2GK1vY2<^0>?1Jz32{1UZ27W2ik38S0'W0{S0er2o310v1rn3P<26K3Iy2Ok2n;0zc14J1rV2d81%^0,v0]{1FV0w$19Z2D}0%K0+^0pH10=17r1P50]00$v2e'1Zf16#1h11R,28x0={0?X0mF0%i20'1p;0@12bI1(20Yf0m21T#0&m2b>11}0;R0yn2OW1pZ1eP1im1dg1dr3K51e:2p71Z?2xl3D(1P{2^]3oQ0(Y2cd14I1N$2P82,B0O^1&X13'0:#0@,2Gh1(!2F?2B11c<3GS0/60oX0>013V18c1111WX1U00uc2u>1,R1cN1sf19i0&g0;81uZ2^b1Y>1cm2Qx2iT1ey2U]1Rh1890$[0'*25o1Nu1Up0$p1G[0he11L1'(0m81e91[@1PW0]m2H10!o16/0pq0k61O&0)I1nJ1VK1Iw0@x0q#0([19H1Jw10$0+a00f<0mW1]k2&31uN2qj2(02t]1<30}#0(*21g1lE1H'1N*39h1+z1v:0[!1M42>P1}H0>j0?Z2%)1lG0sK0^Y1in2p01C20sU2?Z2(Q2Z+2dn2s81c:1860,c17&0@/0{40;*1C#2xW0@1aZ2gJ2R]1d%1J50qj2KU11G0i;0Rl1+k1m(1W'0*G0aR1IS20r2ru1De1920hI0;S1O(0#z1n+1Wy0>+0xb1B)0y,0jI0>h1,=1{}0wg13L1PD0dA1<;1DL0]d2hc26G0@i0Zg0;'0ZI2{z0&!0i]2+}2Sa2cz0'92F]2/]23t2n=2CR2$h2qK1$^25O0*U1^t1qh2ZS1(;1ny1HH2Rp32C1Uf2{X1M931]2HY2jm0]=0@t16!1Io0>}1LE1fO25N15e1pk2IF1NW3j;1Tf12*3b)0$824%1k124212#1Ys2P91YV2c'0d01k92sh1g20;91fw2oK1+^1Gj0zf0}:17A1?k0*60^,1730;K0@[1:71S119]2p<0uH1U]1mJ0}o0qB1$!0(]1/*1i)2*$0YV2ET1Ef1&m2250(p2TW29O1541dl2bf1Zw2LX3Gj1bO3281tY0sJ0op0!A0xf0od0pB0u$0/?2*[0'=0rT1Bf1r*0&O0[N0nc1k31LS1]q1mK0J32Xy1D$1Kb0=G1ai1uy0r}0zi1X^1:c1?o2S=1Gh2ag2$C2FG12L0a31X:29<1U10>Q2f]0Z)2$x1aK0Lb1PY0x=0cM1RO1Ki0C0wk2sj0v!1l2qW3V20:W1+Q1041A@1n:1p:2B[1'}2630@W1Gc1&S0{k0SE0uq0q>0+d1*02@F2mC2*V2E#0zn0it2qP1yp1390((0Y{0dx0pz1kl0>q1IE15f0O&19r0Y[0fM0lZ1K>0#51ca1t&0'k1Dw1%:1Jk1OC3Tx0[t0$A0o{21V0N72uw2a32O?1#812r0>Y31#14j0{l0!W0@]1}[0h,1dU2<=0n,1qa38/1Bd0&01lC0so0fY0zB0/31Mu0f60wV0]n1&z1S$1V}1+u1s/1Y?1430*l0iM0Q;0s53Z]1MY0,>0Xi1%t1&91We1c,1d^3V&0t00ff0,91^g1PG0!&0#s1w?1#^1>F0*91=%1hP1!c1{h0p<0Q%1>W0cZ14Q1ae0=012(0el0V$0IR2u015t32h0W#1B40*y1u90$,0rc1iw1fm0/y1L#0o$0@c1IB2ZG0y61J(0r$1{X2G81y713D15y0>10q31!G2kp0@0l^1JO2AD3O=2eV2X[1X!1;32LU2Bq2cv2BT1=j2UL1571G]0z)0sH1D*0dX1Ju3A)0vf2Ns0x227R1(u2l]1Sw1p)10B2O11wh12N0)212z0,l0+H0cz0^<2L?0no0U!11O1?H1J60^E0e812n0w(0cb0gn1E21ZY12>0Wd0h@1A61!;0oC1LM0,M0lU01710^1DE1xi2+#2M12++1u61&70&:0(w2@<12a1UB1Z^1j;2Ht0$w12I1en1%*0jD0)J1JP1O[1k$1sG16K2*]0cr2ix0k!0'+0=K0Tt0[C1&d0N80tH0wp1cn0V{1R51U?0ii0YI30k2:t12i0kH0$<0'S1nN2GI0kd0yR1K/0A1x;1Nd0%r1{%1>%1M)1f!0+n0;A0N>0KS1mM0)E0&d0,52[^2Y&1!o1S&1Vh1Ho1dO3B=14R1Ny0+I1xF2A}1fz0mn0vU2ko1eQ1tm0>40?I0qg0/s0#T0cO2C$0dB15O0s>0>p1SK0{Q0,r0#w0hb0sQ1Fj0lH1e20:f0gz3UU1)[0[c0+V0<^0{=1D(1g&2N=1}01201g@0]12Qs10i0Lu2%52tm3SB2Ae1}N2]52v?1VX0*J0y72He31:0@}1>D2Ki1aV0]F15Y1n#2f{1*S12J1pH16U2kz1)831f0=11P%1jd1M<0z13bU2Q^0!:0Vw2921L:1Q#2Il1UL0?x1<]32T0Zl1GN2i612A1lk0(61YL1Cw0t>2aj0e'29S0%028Y1v&22u2=G2fr1210#[35*1}y1&811y0>E0^80,h1F*1/e0ut1Al0>U18i00T?0PX0k*2]90)k23v0y819^1SG0)Z0/T1#{1e%2GH1pd2>*0]t1Rr1Qc1]91z)0]60yC0w/0't0l&0Xs12o0g60ms0tx1Z/16,17=1Pc0=20va1[;0QF0);0'X0N!2p'1zP1AL0]!0YC11o2G/0/00vb2GC1YB15T0/m1t@0}X0$B002T71AF0ra1z62[I0Z00pp0nf0h*0QW2l?1W,1Q*2>H0%*1xN1Lu0Nl1I;1cp0u/1Mh0yv2aC1@10*@0b<0d=0tc0#O1}/1$B11%1f%0%o0h$0eF0*,23#0VL0Bc0lL0yj0/825D0]A1AP1Dt0{A1J=0!00Rs0=H1*'12/2nj0]o0dT0#f3GJ0pJ2::1He1D21Hx1931h'1#S1Iz0>#18s0e41yH0[I0zG0$C18q0/c2EP22F3CK38C1'g0$+3E61Nr1E[29B0kW19V2^510g2f/1Mr1Fe1sH18m0{}0#B1lI2]11o<2B]1?,1'$0m'1li1Yq1FU1+i2^l0@G0$a0{F0%,0p(0rB1Li1DR0DO0c80I(0iR1U#1s;1IC1Az1dD1PZ1X$1)Z1qv13[1QY1)U1B00v[1)A1cF1701?10/O0vX0x80fS0XP1w511R0]r0,m0jW2jE2xO0q]0(L23F14:18D1s#0Vq18Q0a/0R^1Z(2Ng16f1d41tB36I2+!2810<31PT10D0#/1p>0(r0><17;0de0nL0U00md1ly1rJ2?h26n0jO2%I0f^0eY0'^1vw2VZ0&51<#0sg1Ge2co0#J0kn0pw1QP0yw0@m1I70)L2r*0kx1^#0))0}&2ui3Dd2ZX0$m13Z0+r0;]2*N10M0hJ0Q31s80&D1RQ0wq1HI1m)0*e0xO2Hg0Fy1=00XZ0#U2UE0sm0#r14}1}?1kh1ZQ1?O1q30Ok0,S0xp1Ky2%F0k;1#I2c:0p'1]$0pa2oC0n:3371V$1]s1CC2VC1ga0[T1YW0wJ0hp0n^2s;1gm0ok2A80;G0o10pC1Z<1jJ0rQ0>)1C=1ZH1l^3U20rC0^t0[G0^]2lX1q^23S1Px0<)1RD1Ss2yj2AZ1s{1Ej1B=0>216L0rr0y10(E2]U2@n25,13S1eL0u+1Oj0jP10(0'T2oH0!y0wb0wi1C63fP2D!2Pq2z,0]v0Dq0f10n!0x!0D&0Eu0d40RG0W31;&0a40Xm0bo0s:0yo2Xc19t2xU1C50pn0:S0z[0+s0tO1Vb1^[10N2T<0fr0^Z1mF2w+1[{1}71XD1yd2kh1x12Xl1^M1o@3151lM2JE2@b0eO1B91Ff10n1HO1pC1220[R16F2I71(?1Um1$21}%0)=1;l11*1As0f&0rK0Ys0jM1vq0ab0${2[T1M>1FA1In1V81CD0<(24Q1632U{1oQ1MV17y0@w0Xe3Jb0oI0!J2KB0b?2Z>0lm0He0lM0QP1oo0X}0?}0E@0KK0;B0j51[M2/D0/t1010$N0SL0@e1=p0YR1cU0=30z&0PO1>Q0Xy0hQ1Xp10%0dP0du0fn0^z0t$1g30si0*D1762wZ1W)1:P0HM0hR2b00UD1By1/32Fc0*%0/&0TG0:k0F71@V1>u1Uu1=s1&61zu1Hp0P=1=(1F(2w*0cJ1Ng0ij0eN0pc1!/1N!0,:1mZ0z]0'50)R0;a0eU1T82rv2Rq2TL1kA1Kf2%&1Z,1Ke2iA2e[14a1rM35B1t?1=>1s&2g^23K[2pO23Q0(M2pY1J,1Wm1c*026L0e(0o[2k!0U}0VI0aW18J1=^2^*1r[0/]2dU1]C16736$0U#0k?2;L2la0+K0%^0qZ0Yj0S%0@41550WQ0gu0S*2!T0^N0@p11f1!r0/40yx0j;21l2NC1>>1n00>X2qT0s81F+0181T@0^X0hG0Y70dz0@C0yF1rS0S]0q{1@h1N;0q&2?w1I(1k)1At1Yt16%1;*24M1rE1(@1CN1Pn2Qa0z:2Yl0s@0qC1{e0@j1Cj0)91Ox0:<0>51D!0se1k[0l#0,Q2>r1lV0I@1[737v1ow1lJ1wv0}01=33Jw0j?0]U1hW1{j0]20nJ0sc0;=1022#v25+2z51s=1V11mQ1Xd1O#1DZ0}50$#1CQ2dg1Yv0]c1E{1G}1bU2z>1eR3ix1xB1H^2?e1{m1!71op1KH1D;1ep0'U1d10>M1Uz0x}0ig1{E0V%1Xl0dY0)31g*0zz2>J0T)0kz1j30b=19O29q0u=1hT0&y13K1]w0s}1sw0n40,U2Sv22O0yX1lo1pr14e0&V1V[17#1FG0IF3D<1#q0yp1L$0uV0YX1H{01Vi30t0[u15L1Da0]Y2tt1>g0bR2i;2Iz3h713>1tw1Zx2KP2hC1d>1;Y1!f1$31tC1iP1}}1/21?51;m0uC0!M1Vx13]0tg1VE0hA0}{0i00uM18y0Wa0Q/0[U17U1&s0i124i0%T1J91hu1620aI1Rt0Xz0k/0la0X/0{(1XT1E11XK0jk0WC0gp0yi16+0VN0@<1lA0'n0nR1h,0yc1H01TG1[y0pW0>g0{g1{61:X1(W1Le11!1MA2Fu1j$0}H1xh1jj0(t13P1/M1M51C/1JA25t15P0vG2e<0IK0Uc1/80U)2!'24U2Q71AM11S0bM2h631P0(B0e+1yO0;v19A2P<0c'0%A0}11AA0}f2KR1Ip1UT2tF1[Y0)t0wK34@0V?0*i0:&10e1X71*$1n42G}19,1Dy20R0^O0=h2Z61Fw0/514C2ac1p@0gf0!C0mR2KA16R0Ui0SM2C<0p{1Jc0JP0k70/}1Td2RY1Ly0@u1A>1Vj2Ep1j+0i518G2Ly0r<0r/0TB0x&0gh1m80v^0xE2QP2Qz17O0xW0my0#40<60rR0vV0$b0J!0fs0TH16v2w00u{0S<0ch0=r1?h1340mk1M%1@R15n1U*13i18R0Ni0wW0qc2lg0=k0/E0>61va1K%0!r0+p1(P0?=2tI1Zn0^51F22mu0/v1LH1ac0bg1!O0p$1yP1u[1Fh24Z0Rq0F=0LT1^L0c22Hw1T91EN0?10p:13A0u,0$}2dY1!^0eG2KX0y&1Cp0l$2Rv0UU0[V0vg0bx0yT0[L1+#1Fx13q11P1z{0JF1wb1Xo1v{3Ri1R%1Eb0wG2nA0Of0th0[(1Yn12U0OG0Cx15v1F'3J'1B[1Yw1WV0'y2AB1x#0*j2&h2YO1FJ3JG0v<0>30+:1X(1/91iQ2%x0d*0wl0$(0Nx1&o1IP0Ow1NS0?60;L1/Q1d81'O0>O0QZ0q71wB2zb0;H12[1e,0](0#a2Xq0xI0Zh0aM0b40Pm0PF0Z%2iU2f)1>+1SN2L/0=<2A/0o@1L41/W1,t27S2<(2S&2aD1:q2I/0v+0L214(0i60Zz2b?0?K0t]1+M1ia0jf0v&0/(1M+0R&0l[0QL1QH0ZA1Hc0?a0=/0*Z0pg0(F2vn2SC0{J2?I3Pv1pl2EX2vu0&>3Jt21{0Ar0S00TR1Ez0o]0av0T10S>0U11/D1nO1fG1Rc0{#0;o1GD0w*0K?0iU0sv0;N0,*0:)0]&0w!15&0Qm0P}1aj0rF0%S0Ij1Ek1Pl1?N21C0dU0jr0w=2B:1&q2q80Ie19l2lY1)V21e0=s0pF0&?1YG1VU0m%0Rm0QC1fU0f{0Z10KZ0qD1i:0^&0}P0Vj1'?0*s0Cy0M{0ak1:(0P50(T0;20+c0Ot1/f0{p2Iv1wr0aS0;X0#I0@#0{>0t%0e512t0?b1X02a@2mw2a(25k2yR2X{23w2Ej2d/0(^0R:0(k0H{1xt2;Y25x14W1kH0>n0zW0zg0k:0tP0w01n>17o0*t0XM0MM0db0L@0Q:0ZC0Rd0xS0L%0zS0^/0k<0xw0(c0(&0qx1z/10h0MS06I07F0h<0O90BL0CE19a0r(1dn2Xu0nM0rh1NQ17R1v(2G%2J+0?p0MU0Wh27#0i)0x>0[?0v00dG0Fs0XI2H30(x1sC2d>1+/2rQ0e$1h20]B1aB1p%0{%0;w1+W0:]1Q(1Cs3I10)!1GU1?d1!m21t1Qd1$b18'2nZ24d1A)2PH0ft1oY0%g1q,0,p0=$0mt0fF0u41O/0r&1%!0rk1VQ1o20ac0fk21[050T71?;0d<0nT11<0ZD0YP1uu0z519T1KJ0XB1WR1q%0ir0nv1Y!0bp1^%0E*0E+2rr0Ik0NI1F}0PB0Ft0'B15Z19L0iu0ux0S10K)0oO0Je0Sg0hS0N]0NC0Yt0uD0pM0NO1][0g,1t]18V2Nj0,X2L90HZ0[o1zC1N?0}$0^e0{M0}Y0H}0xC0gA0BM1vP20#2oX1O]0a}26@2NN0]i16g1Mz1w00zO0ug2g(2IK0zt1E$2$m0Yb1=O09=0>u17*0(40%c0a00kc0RC2^)0e60fy1L%1U425C1P<0t'0z31E323B0Ck09o0KL1PP0Qa2J?0l}1Se0<@0k10E)0j!0v?0'E1r60NZ2PO1QM0OI1r30U@0au0r40mz23r0&@0O40e,0pN1C31cG0b)2?=0nU0B}0jg0bL0kU0ls0^j0{'0y$1+827B0/M1U60&Y0nK1zY1ef1M;0z*0{X1F<0w20+O0:c1Ce0UL1o=1Bi1EY2qR0Qj0eu0@L0VO0[@2580$q0w'0(@0jQ0e[0m,0yI1I!2J10s30uW1u,0rY1<{0cv0^)0?@0WU1Jr3ZU2$D2wB1*B2P@11]0)V1)k1/v1MB1Cq3492l%2r#0q+0V}1vL0IB3EX0j#0=}0;?2i*1hL0wB1(*0Ne0E60Y10E!0P60z?0dQ2FF0@Q0sT1Uy1Y80Uf1O=0lx19j0tq1ZZ1kZ1bf2aL1Sv0}A2ab0sV0,A0>G1=;0+D0zX1C01RG0m)0$=13F1@q2Uh1T^1ax30b2o{25l10z0xt0zo0z91*>2>f0xx0hD14G1@r0T21R#1f42M#1FK0Wb0KM0bZ0zq2;]1681Bo0OA0Ut0c/0fZ0ZX0%(0&Q1/&1@B12]2t}15;01@d1SX10b2qO2}31yQ0?Q1J>1K90;<1DM0kZ0W41BY0po1GF12=1PU0?!1's19415h0:t1fi0!*1r21A71MN0zj25H0(l1![3oa1Of1e'1R00V<0(u0ki1h(1;;0Wu0rZ1501F/1VM0a60+i0qS0Lh0Ud0Zm0YT0wH0=%0h[1b60N<0+t1qG0Go0Hu0(#0zY0j20aX0ih0LW0Wv0e)0hn0P!0<#0D>0c[2F}1a}0fz11@1m=2O{0oq0hc0t70w<1:f0ou1h2UA1m+0+W2Se1/p1g(1Y^0Wq2212$i0zK21c19u0;x1z%1vC2fp2jW1Du0:;3KZ0Xa0i20e=1Xm0gg0js1kD1Cc0cw1S40l]1fx0^p1t$1ox1vt36G1g/2(u0gE1X&0d(0v*0Y,1Eh1gM0W50Yo0[,1W<0>K2LW0&b1070$V1Th0:*1M823?1V=1iI0X20/H0^;0@Y0oJ0m71/H0/h0$H0+80FX0RL12{1bc2TG0b%0J+1kP0;,1b&0jZ0kj0q$2dL0S(0RM1=72K@0C?0=A2g+0c>0qy0Oo0wE0xy1X]0}?1R;0rv21(0m00L(0%V0nZ1v42F:0iV2Z?2TB2Mx1>E00Lk0DP0MK0Hn0O{0*{0r60zl0(80%231s2Cv0(U0>d0)42Lf1uG1pq0j*1X{0O/1p'0nF1c>2=K1D,2852xV1d(1@n0$W1TX0yG0#b1=y0vR0tC1*r1Ao0Hv1AK0)P0Am1Sn0N^0W,16b0Nu0[+3cV0Pr0Ka0I60Rb0gU0z(2]30],0[50^C17v0q,0{u1KB0d/0NJ0a10L:2S:0Uu1Xe15*0^[26B11X1I51n;0Fm2x=0Qn0bS0HE0me0,)0;b1E:0m[0uX1c[1CH0<$0hi0o30q?0p60fL32S0XV1z*0w)20n0iz0$g0L31Oo1@o1zG15l0'd2Cx10;0}i2791rP0ba1%90/d1Rd0uN1$51rl00@N2WH1ye0N{0HI0De0O,0Wi1490mh18W0xh16C1Q%0/$13u0wm1og1bV0nV1[x2]k0/+2{42L+1;n1A{0Sz0yY0?z1]U0HN0ke0o81^e1vZ0Xx0KJ0w?0?l0iA0m]0$&0P)1fR0190v80f'0r920$13#0%v17Z25E1Or0u}1GH0^*2xr1n,0/C1F#0?v1UG1Ud0x51&;15F18,0oz17e2d=0}r2Mb0SN1Gz22%0sx1#U14^1^=13(1wc2Y910/1Ij10&0L/0lz1Xy2(S1X?2301FO15u0Zw14T2Dh2Ky0#l0?#3Dm1lR39g0k>2JM11H0c*0y{0&(19s0lf3I62Be0*m2Vs1(31xx0>y0Kr0to0mE0;E0jH1)@0t)0$x0*P0&i17a0h!1E(0'F0%=0*S0'G0^(3MF10H0Ug0U<0z00f20vH0im0r;1rN1gt33825Y1:&0%51$>1E01&I0eV0+P2a'0lV0Qx0ol0$]1(#15C0XE0/P1c^0NP26T1t30m>0yd1*u0=M0D10y*1Wp0om0ad1*[1iK1N70Y20IS0G!0G?2qY0k80b[0J{0f30El0f/0Vg1T61DY0Pw0n60f@1U@2640gQ0^+1C'0)h0RF0ma0vI1:o2t(0*32mQ0/Q28E0W/0Jj0hK18&0A^1a10Wr0XN1F>11Y1E90vm09t0b:0s#2#d0dZ3AJ2QO11C0fe18>20A1vc10f0lN0a[1}m0tT1bp0zv0x*0N?0^{0Qs0qd2Ea0S'0}'0JX0%f1@G0MT0,60!j2SK0e21AQ0cx0=I15}17+0Ex1Za1)+2RK10d10ix18f1O;00q*0D:36l0{m0Td0D}0j$0A&16c1^o0U&1Pq08c0uQ2f;0Jq0ks0tn0&n1=R0oD0n70$50v)0Sd01B11p17C0CN0A$0n?0X#0Ki0^r0]*0zC1vJ0qp0??0<&0;#1g61mm0[d0U'0:h0X70gi0i70ka0K{0!H1U>1BW1ID0WE0y=2ZZ1G'1Uq16$2A)1Q90T&0HF0rG3V+0M&0}}0v@0KR0j>0lg1cP0%B2NT1gJ1ls0ej0mx0%)0>=2VF1?#17h1HB0Z42IH2Ck0^60U*1^B2+,1KZ1Wd0vD0pY0vB0;t0,H2E?1?=0*u0wX0%C1uI0gV1AX1+J1eo2H22Re1zW0Wk0Nd2Z,1^W1X%1%u0lW0%O0b'1Gm1O01lK1DV0YJ1OS1oP2r]2v=0K}0Dr2B#0al0N90Y=0OW0x;0&)0Q40ju10+14X0hh0D+0fT0$>0l<0bC0sZ2BR0qF19{0}u0!'0:X0>Z0eT0u*0]%0Kp0i30[=1C[0f>1Oa0@q2T}2#M1y@24]0@$1+U0VE0UM0Ef1m20V81de0mH1zZ0IL1301:x1fh2F$0&A0@R0k{0ZQ0VJ0B'0MB0PD0D0&c1Zh0>;1mk0p>0@k1eN0cN1MW0!b0'71jY22<0LH2(?1%}1'h0hH0)i0e}0wO0WV0!R0S?0St0C;1E<1ea3h?0dL0Pb0m#0:$0Vk0E]0n>1YJ2Cn13B0mi1W#0+'0$h29'0{W0,Z0bi0sq1DP0*V2Mm0:G1R!0#t1LC1b%0Og0oh0}I2dP0d$0r10u60X!0fo1TR0k92I50>:0hV01C0,%0}621A1R}0R]0yN0OH0RI1J717!1br0N:0i<0(>0aN1$)1*N0q60S)0}j0*&1P80=^1I31^j1[X13c0=b0[p2cR2I*1hH0!w1;i0NU0FJ1T20E{0M00BX0KO0d%0Z[1?*1T71Sa0V^1gw0b80iW0w90y30ko0j(0jR0Xn2gU2d)1pa0vE1(50y:1HV3I71zn0T[0L81OO0{G1XI1{P0aT0d52$o0Vn1PH0;(0i{0)00qr0t@0^114A0]@1D91yA0K51fy0x,1Tn0Qg0J?18$0@l1LX2861BR08k0Fz0)m0fw0f#1<@1L{0(R2+$2ku0^@0W$0:J0fN1/@0wn2h*1Ew0R10oK0Rt0w@0{K1c71r]0]w0Wy0B*0X%0u:1Z&1AG1Vs0dv0zA2@z0zp30626)1p*26/2Gr1X40y41u$0,i05:0Y!2IN0#C3ES0s)0Ol0]j0h/0H)0X'0oE0o}0H+0X008#1ED1DG1x83L<0&]0sn0S[1fd0gR0gI0#R13)15+1jP1tn0mu1fL1E/1Q20Sh2221Wh0ic0zw1JJ0=P0}v0pD0>[07?0wx0oM0#u0)F1LD1Bt0tY0[w3bw0t+0a72u;1Q01';36H0p10dn0)A0NM0q10zF31=2F/0F$0*(0$J0{!0Ti18H0TW0v}0n010:1U{0hx20,0Sp0)r1LT1370Gu0{N0oe1rm1kI0zd1v=0c@09<1kz1n*0!U2F11nr1Df1WY1H&0vn2k:2Ap1W$1T+0,(09k0;W1cY0r'1@P0EK0(Q0Pi0CO0[61ns1Fk1G21z20hd0lh0gB1cO1Zu0=J0G50R'2K>1HF0Ny2J70lq1Aa1CP1;82QG1HN2Op0b}0hv1]#0{b1Dv0n@1fX0#>14)2fX0O51B>1Dz11N0%z0NQ2En0^S0mv0{V17w0hN1];1{:0T90t,1O?0{T0f,0J;0cK0M,0zr0%]0ym2U'0K*0jv0I71240kr0UQ2A,0Vr0ca0R{1Xu1CK0{c0Nm1$l0Ho2e)1aS0pK28t0YE0Ru1PA0^0ef1$}27k1OW1{>22X0#+0SX0h>0j61r}2Kl1A#1,L0yD1tt1Rp26<08i0eX19}2As1>$14+0=X13j1F^1h50:Y0U,0=B0O70Y}0^L0Km2Q[1RW2O00Ha1N30Op0Gk0')1J^01D2gb2Cm1%'0f]0Yc0zL1lO0uu1E?24q0q(10F0z40ZJ1?e0%F0zD1J110l0g'1DN2Bo2HV2RO0RA0R#0pT1+q0C^1>i0sX0'g1b=2jc0iP13!1OG0cT2J@0kl0Yy0Ag1u@0nX0p;0bl0'Y0!m1M#0'N2n^0:D22#11I0xc1N<1HK0Mw0L+0Tn0Zb0T'1sq0Vx1(917'1RT0u<1RU1&u0'b1*40%N1P(14&12:0O=07G0KF0Gz0SV0S+0g+0X30RQ0zP0kk1u#0s[1n!0mS1@*0,a0aU1JK09f0PG0An0?22JQ1O'1[j0sS0j)1N20!$2in1'B0p+1HZ0By0o/13%0W11(K0H[0X80ZE0E}0Q=0@B1h)0T%0DJ0Bo0GI0EB0V(0E:1X30NN0l*0jw0v1I<1j]0Po2cZ0T}0aF1xb0lu0b/1W[1QI20t0Mi0gv0{d0##1Jy0hw1qB0st0o*0V30ky0Tz0iE0c;0r70F'1?40HU0Nf0F,0HB0b50SA1m51/=0jc1Vv0a+0X(0*)0xT0q%0&E0cX0fG0m?1e}0iF0uR2Q91xk2A20'H0Sl2/A0UV0jl1N80E#0nd1+}0J:0c#07&0#$0Lg0mO1wS1A$0!20*H0m+2fR0lI0hB0c]0l90wP0:K16?0Gp0vx0EH0?/2HW1az1L@0]D0}K1C>2M!1Am0,b0<]0Z^1pI1lB0+A1kg0lJ1870/o2h21EE0cf1vK0h]1]D0uI16:0t80jU0Ll0Eo0}B0*}0y}0sY2Fl0M=0;31>]0z21aR0P<0@X10s0!P0uo0cY0P'0nG0:81V<0:635m0Qf0V'1h!0a%0rL0g]0nk1SE0dF1O<0,/1r70q:1NL08$0)b0ap0vy0e00PL1aT1Ec0VM0A90U50br0>>1#F0e%0}E1f$0pG0M62!M08R0eL2Ab0l?1p50xL1i918Y1gY1AS2it0t=0*:2m,0qU1QB0ik0Wz0yq1950)d1BO1ZT1yS0k0d)0O*1=90Um12y05Q0W)09d0IO2)]0^708p0CW2?!0]?0E20(Z0PR0c(0x$0{O0&u0R>0d[1ki0/i1mW1JC0R/1nP0+e0}T16Z11D1N'0c$0h(0f80gM1St1310aK0cQ0Yw1MI1EU0S@0b20v{0U>0}g1O50vY0'p11d0pX1rD0hq2C?0k@1^/0T50nw0G70ae1/N23c0PJ0?)0Em1Q=0t91Nn15[0^D0&T0?40a'0aY2hg1Cn0SY1dM0#10&e1fo0YF0Hw1>30hm0Zi1560VR0Qo0nq1co1Q>0=n0}%0T*0dR0Zc15o0#=1zL0)B0H?0I]0}C0ov1/t1vR07z0RR1!50Pg0Q@0$60Fv1Q11Tr0XW1b50(a0TJ0Z515]1@I0J=0J@1LY0mJ0As0CP0Ja0+@0Tc0Mk3450co0}Z3CM2o52dh2gZ1k}1!U3mk0B(0bs1Af0Kg0'K0JT0ys0,J1vf1sz0p70lF0+#0Iq0bU0G}0F%0GS0QJ0kI0DK0RN0GA0EN0J40HL0SZ0Iv0GO15^0Qz0h40fO0Oa0KG0Ae0AR0Tu01E09F0FA0AG0BI0Ec0Bd0JG0Bs0Q*0CR0{B0a^09&0t10KP0G80DZ0J#0HW0Xj0Bz0G@0sa0Mb0Fg0BT0VV0DL0CZ0GJ0Ca0Kx0TC0Tg09T0QX0S{0r0Su09>0P*0MC0zE0t60VA0qz2Cz0#c0g{0YU0of0X]0B0cU0J52SD0]X0I!0AO0B]0CX1dv0H40J}0I&0+j08j0RS0tI0W%0Ry0(b0EF0/'1ot0ie2u$0!D1D:0B!23K0[S0Wl1DS08'07u0EU2Ml21Y0Un006c1yZ0ob1x$0wN0=60IJ0Z'0U[08d0O}13k0o(0FM0Hy0770AC0yH0I30AE2gf0JY0,D0*00p[1s60EW0Gf0E81Ei1:@0Ii0,y0n11F50FY1k?1(x2am2Gy2T]0Tp0e&12B1H+1FH0dH1ev0f90F!0f+28>0T80Vb0$r0MN0I40}V0sz0P00Fw0A%0Zd0Nb0UF0MV0Pc0Ah0Ga0Fx0OB0p=0t#1hQ0H;1oz1yD1Fp0Fn21%1[z0UN0B<0Ph08A0Cs0H50+G2H)0/w1*g1kU1Q[0xq0FT0eB0S30Li0K^0?C0Q!1C80%D1Vf0^c0J60KH0V41&v0eH18E1X/0C71=e26f17b0m!0C:0DB0D40bV1m#0T<0Qt0Gb0TA0B>0Jl0yb0z{1a00n#0}<12C0dj1ua0N@0D$0M:0ZR0ME0PS1ND1d#1Rq0VF1WS1I^1mD0kV0QD1K@1B50vt0K@0m&00w>0%P1#O0cp1M20,=1FX0SQ1w80Db1)f12D1WJ1Tk0PT0V=1611fQ0Cd0U;1>&0Z+1)50YL17S13I0D20Ob1ma08z0d:0WM0&B1CW0s415<0Sv0d?1081qR0Ro0AV0Zq0EV0&j0p!06R0Xf0!c0K&0c90FN18A0L'0kp0J%0tp0Ia0D]0Bt2Lc0=+1D%0x(0XJ21#3K}2U41nY0;Y0CL0WR0:i1z+0:F0@610Q1j'0d>0[&06}0H,2nD0M20vW2Mt0M*0O<00h{0z+0#X0?L07O0d60bG0B?0A[0pj0ai1AY0Fl17?0um0Tw0Mq0@T22Y1*s0F>0i$1)l0Te06J0C80l41UU08Y0dC0BA0?&0WG0Cz0)S0z@0Ib0#1O$1p]16h2L]00Lq0?R1iG2Q!0Ct0&o0YZ1k<1^809+07<0N00rs0Sw0sC0Ce0fx0B%1'x0Z@08%0Ua08?08V0dg0f:0<;0bz0w31?I0u>0u20e@0pZ1Jm0g}0ws0EP0'<0sG1gd2cG1{'0QN1EF0O60GD0c60Da1y^0u%09*0^:09{0fg0H*0Ml1Oq04!08s04c0E00fH0fl0<02vy14U1U&1>:1E]0h00*b0o<1e#2SI1^K2DS1p{15{23,18%0{v0O)09s1061T%0Xg0A30w[0N}1A%0P10h10Qb0Xt0V[1eq0:u0F;0Q^1,E0nx0i,1D509h08e0:l0tA0#h0G/0Cm1tf0G60*K0Cn1UN0Pk0Qc0mK0?j0il0M[1BA08t0nO2T326+0=a0wL0Q80]J0k]1b[0cg0M41L}1wX1Gt0[;1Yd0Uq0NA0,}0P>0tB0vF0]/0j,0sf0]N2Vj0MO0iq1771=H0<{0oQ2LS0@Z0o>0v#0O?1fI0R60kK0qf0Y;0NS0M@18M05]0Hi10lK0Zf0^>0ph0i/0):0o+0wQ0+$1u:0Gg17H0CI0UB0Dn2N%0My0VX0QE0zh0R71XU1h[0j%2Lq1dV0V]0?e0qV1=I0aE0#!19%0oc0WH0Mf0q}0]I0MY0aw1iO0{32oB1:'0(=0x60KQ0[W0vo0Xc0Zx0SR0#g0Dz08G0wR0QK22U09K0RJ0aA0d71$40Tm0&^0(V0*+0J[1Nh06'0n20xX05R0Il1LL10?0H#0*Q10j0DQ1ka1KW0!h0RO0Ic09l0ja0u70FK0Pz0EC0v105N08Z0sD05o0C40sB0m*0D%1Ir05w0Gy0SB1SF1Cd0Jm0)e09D1c&1^u0jz1E=06X0&K0c=0Qq0Id0I>17J05l0Ou0+u0q<11{0p]0lb0JW0i80B30j@0Aq0GX0q/0iL0U+0800*n0VS2K:0j/0*A1En0O$17B1Q;0Hp27z0iQ1!C0mm09m0@a0}Q0=)2^40Dc0Ng1T=0HR0^f0)$1Qu10R0D(0Hd0D*0r?0P/0AU0Ws0xj0!@0%l0DR09Z0Uw0)c0C)0?*06+2J60ZN0Ww0E90Gh13T07>1gn0)w0�dm1P!0Lm0C[09g06=05u0rx1YX0E$1q#0D70u^0Lr0Sa0qJ0dD0c30?D0En0KU0e*0EX0Bm0XC08B0H(09L0b*0$n0M70/>06o0Vu0bq0uh0XR0vp0920GF0F10ps0vO0mp0N*0SS1j&06Y2fk0Dk0aB19)0=v0TS0FL0M'0LL0+o2Zz0GN1C$1av08,0Ac0j10)W0GQ0AS0YK1iU1]l1x30d{0Ls0X$0/l06N0OT0Kn0K#06t06S0es0Hf0q=0tU0wI0=y0,70+(0M#0AD0hT0+M0M;0Mm0cA0r,06705!0VY0a&0Sx0K[0ST0Nj0ED0YG2VI1=A0m32h,0781(^0JL1jh07P0jj0!,0!I0sk2MH13305?0dw0A<0o=0lc0q^0E30TE1W^0CA06y1U20{q0Zn0L^03x0K,0q80(d0$K0Zk0RK0RV0H604'0@o0JH06O0BY0;}0N607I0Iw05G06Z1A;0q40ZF2YN0Y&0vd0*g1Zo03O1'S1ws0ey0m10WT0S=0UJ0G[0Ld0qO09;0Xu0h?0Gq10O0b!3Bp1s(0TL1W80t!05p0@b0UX08H0U70Y30Ly0B^0!=0ll1W>0+q0I$16i0GB0QQ0wt0b60ID0Cp1(Y21o0y(0Jd0Y?2Oe0Hj0c70C>0%{0kA2530Ew0Ix0ZL0DG0'u0D?0l;0iY0bt0/%0G*0='0CJ0#608S1QJ0k,1h}0O12Lk1vv0x70U81fN1ZL0Lt0GR0F*0d30]O1J#0Df2Z=2C%27?0R+0U$0,I1Xx0?'0oU0WP0T]19e1UV0@H0+20gZ0gC0I%0sr0iC1Di2DE0BJ0Gv0Du0C{0E<14]0k40])0Ci0[}0Q50QT0Dg0IH0Fd0Sr2GL1#R05e0Hz0Kk05212p0dy0!!0Qk2Eg0650Z]0Ir09P0Kt0Wn0*70AJ1ii0xk0e70Ve0HP1rG0hy1Vn0GC0Y806j0!?0#i0T30Jx0#Y0D50R%0s+0Tx1C,0JR0jS0N(1@l0Uo07U0OX0YW0p%0gJ0/j0CY0Sb0Fe0L!0TX0[y0A*1KM0+=08P1eT1KU0RW0Az0BZ1IH0{n0*10a*22t0O>0{$0at2Ls1({0w40I+0SI1!#0G10Jn0RX0JZ1*e0d^10S12!0610d;0G30LI1e00Dv0U32&[0;z1}Z0;P0Z*1NU0PV0OY0Xp0a50Q00oG1j50PY05'0uZ0a80gc0DX17D1*d2DM0HS0Rv0OP07Y0(O0CS0JI1;C0K71hV0YS1d;1VF0GG1Xz0$O06]0H70W{0zT0MW0cB0+y1MX0gK0Bp1V006g07@08@0JU0HK0A>0JV0Yk0VT0kL0n[0J80Lf1%g0621AH0+)0?$0Cu0bT0bN0aa05J08I0Nk0C,0E%17@2?<0Nc0W+2QH0u80g&0Nw0S&0+%0B411Z1NX1Ag1cQ1q60Lv0Y*0/,0Q90Mg0^H0,30(q1G{0Nh0c!0Vh0vq0Gl0)N1:,0c%1?w0p90lv1960XT0G<1qc07!1[e0wT0Ba0C&0ON0N101I1k{0hW13W0hj0F]0I:0g<0Q{2fq1&W0DC0!}0PW1Hq0a<1rQ0h'0Q,17f2HC0Yu0z/0eW0EJ1H90Z?0P@0'Z0G+0Zu14=0MP3Ea0da0>R0Ez0mX0jp0Oq0x<0A:1Hy0!70Iy0MQ0o)0fi0ve0=Y0Mn0+N07D0890Ed0k(0a#0rH0qE0hO13w0jE1wN0p^0y?0Hk0Kw0ZM0?S07V0!#0Pj0P+0Q$0bb1qX0Rr1Kr0gq0,K0T+0os0g(1D00T61oc0A,1#L0@U09(14B0!t0X50Gm0TT0LV0OJ0Q?0F?0r[0JS0<}0HG1lP0A(07Z0+[0nr0SJ08]0Jt0EZ0sM0wD0&k0Xd1B60,!0Tj0B/0Cq0w^1IF0oV1GS19y14o1!K0?q0)T0Gd0Qd1e^0Oh0>]1Ad17i0Mr15i10V0Ms0s91++2i80X+0XX2cU1Xn0QO0LS0OE1P'0XS1]^2vW0)s0Sn0ox0m;1J;0&'15,0vu0CF2*c0?r0hY0+l0g40jF0>e15b11U0f40!Q0{?19p0S$0KV0*C2K}0dq0Rc1SR06F1pM0T{0LC0#V1AJ0,413z0:L1k>0uv0TM2NG0XH1tp0d'03y1P?0T=0gX0*v1&/1}I1H<0P;0q;0Re0Zr0AX0Rg0=E0pU0OK1$C0Dp0LX0yZ1#W0ez0qv0Pq0nS0M514g1Q40'!0=f0'l1Q,0==1xO0r+1GV0Iz0i911g0W70ln0Bf0540MF1!n0?+12E0Ps1j{0ro1S90O@0eM16)0=L0Vf0/D0oR0lA0@O06z0f=0bv0uJ0$!0fD0&0V;0G]0Yd0r:1Ra1HQ1G&0(N0uY0zM1!*1C91TD0ea19v0SU0Y50/q0b^1Y/07*0Gt0]}1x60R;0IV0Jr0bE0k)14>0Om0Ur0dJ0h20K=0M<1eD07'0Sj0LD0k}0vr0m^0qP0Al09)0D<0A]1mV0+f1EV1BS0gs0[*0dS10Qi0F/0J20g*1F)0)u0*q2Bl0Jg015U0Pe0I^0B;0ep07W0ek0i%0K<0:m1SZ0D/0h}0KE1hZ0QH0Q]13=0za0R$0IQ1PE0GH0nP07r09^1l02c10b90/V1&g09[09@0)C0K$0*=1121FE0*k0tv1B'0PM0@D0ag22s0W80an0]^0]L0Ax0J<0eh0QU0G(0J^0p&0a90Js1FP0Q}0Xo0R}1Fi0Uy0,E2WW28z0cq0FO0cd0Qr0aP0u@0w;0Wo0ur0s&0L)0D)0LZ08f0&z0W^0RZ0gk0G>14k0Ta0B@1Fc0XK04j0AW0a;03s0L?06?2C^06h00aq0R(0bW13,0c)0s{2$*0CC0L{0M81H=0*21ld0It0{s0Au0La12g07J0/=0bH0iD0:V0bA0i@1w$0D320c05>04I1DT06$1I:0Eh0WI0qH0J]1>q1uL0S70WW0YO0!F2@X0wY09z0C/0#;0SC09W0Hl0C50FP0Ei0H=05#0ml0NE0Lz0Y:2]r0Iu1M=0ax0Ff0g=1SP0pk0Pl1c30+C0V00JM0Jy0O21+$0Ea0V&0%L0Mc0=!0BF0D80No0VG0A50A70R=0V50A!0li0o!2ao0j00(g0!;0ZV0hE1vd1O{0ei0qQ0R80B51]F0L&0E(0E^0Ks09v07E0AT1f20LJ0Z;0tb0Ku0Im0bX0pe1L80830F(0Cb0g^0f*0j^0O011u0j&0DY0WJ0V60P]13r0U60a,0V71V)0cC0fA0iZ0)K0LE0{70=>0K81U}0Qe0Yl0x90Nq0ZB1Ua0X*0fQ0'V0Xl1B?0Y60cG0iG0aQ0zR0W00DN0]30fp0Kl07=0Hq07d0Bg05q08y0hk0G;0,z0+k0zy08T0U^0a]1(v0A;0YM0Hr0bc0Ju04(0pv08<0N[0'v0S40iH1g#1A}0xZ0lo0y00960NF0Or1nR04007k0YQ0LF0gD1A90bI0PH0dE0/B0EQ14v0eD0<90Uz0?G0NG0*E0F51RL03z17%0Mj0IW0&G1)o0pO0=C0f}0*;0;M07#0T^0cD0Od0#^04404?07l05v06k04l07B07$07(06G09b0H$0@>0C}0In0Si0c40%W0DD0EA1#g0@90aH1A*0aO1P61HG07)16[08)0C<0aJ18?0Uv0Is0y@1Lf0Oi0UY0ct0cR0Eb0VB0Q10gw0Do0K00f(0L<0w,10P0d}0Tr09X1!l0iJ1Cg0{80b&0c50Z:0Tf1F,06<0jY0g50q[1Qi0tF1Ne1aX0HC0MH0O#0cy19I0Y902p0AH11b0Ri0oY1y80qt0W;0R)0T(0^u1G:0ZW0dV0g10j+0PI0Ge0I81J30K!0JJ0Zt0j<0l51X90Vz0bJ0zV0LQ0rz1}908l0fI0G$1Hv0[m0GY1aG0GT0h%0be1bG0g!2OT0hl0K;1ZB21f0#*0uF0vM1Dp0tK0+E0y#0XU0)Q0{D11#0(i0C*0Tb0ML0TN0ET09/0Ub0?M10I0ZY0+60!%0>H08!0nz0FU0Ln0g71DC0Bb04208W05a0Cg0dc0)70!Y0FV0@f0f00bf1{q0VH0fc0j]0$c0+F08[0T,0jJ1/A0Ej0L}0IX0*[0US14w0jC0Pn0Zs0Zv0m(01K0Fp0Md05B1sv08g09O04F09Q0{e0mq0kw0D[09U05S0NV0On1Yb0Y@0Io0cH0#m0x#0i}1740e>1g]1tX0S!1QC0bw1Lz0Rp0W20w#0xV0DE0Ts0C30F)0sE0Dl0l(0oy0[X0J'0P80Dh0Dw09i1)(0FW0F@0IY0h;0!K2Q+0BO0AZ1VC0H:21:0p)0UK0680E50B60xJ0Mu0VW0B:0o?04q0V:0D'1580ER0HX0FF0Av0mL2D$0Yh1kx0Rk02v0fj0o^0:I0>m0%'0iv0lj0/{0Oj0A?1B20Z{0rD0E&1LA0H802c0bB04%0BP2840kE02@0H'0S,1>x0ay0Ai07,03/05+0K909H0P{0&S0mf0A/0c:0/W0%!0W91bZ0k00BB0P#0t205W0DH0H&0cW0QA0Cj0e;1nz06U0{<0jN0%[0p*06p0FG1(w0S81Uw0W<0Sk0r@0$30?70y)0:,0F90'@0)y0!p0/)0[713M0%10?>13d0H%07[0A603c0Kc18n04V08m0CT1EG04905b0WO04^05i04S03T0L40!S07A06e08=0VC09:09]0Dx0H907{0w60F{01L07e0L508q0>70Bu09c0e]04a0D=0DA0DF07f18w05909q03J0k=0WD0D^0X?0fB0BQ07;0A@06w0OU0Jh0E40kM0WZ0P$03$0vJ0ci0C#0mT08n0X90B)0!10b#0c<03E17]0B70G'07s1090NW0dd0:v0N&1pQ17s1}d0AP05L09Y0Y/04607v0FQ0uP05s0XF03R0Kh0C109a0Dd0Ky1B714Y03j02;02d02V03F02A0340Vi0Jz0Kd1ZX07g08J0F60bY0I006i0Bj0900J00H^0X)0O;1k%02H0%Y06u0MI0E,1dp06D07a0CG0OL0eg0F+08D08+09j0V@0r%0n50cn1d'0R20l007006l04t1es0Fa0Ig04b0i404503+04k05g0Ih04y08E03Y0Z/0KW1Qh0g?05305K0*w0o40Ad06{0I90[$1VW0I109#0HH0i^0Cc0a{0kF04)05r05j0^00SD03%04J07]0uK0D!0v$1u/0D;06>0Di06@0QY0oZ0AA0vS0nY0ZH1fY0v,0'r0+00=l0bO0@(0Tk0n/0NH0T/0GE0J90Z(0Vo0R?0u00bd12R0d#0e90%t0h71'40FH0X{0Dm0CU05{0E/0C$0S;0d80Q(0xD0Pa0l:04+0Bv0Jv0XY0sI0PN0Fh03o04&0Xq0B10@I0UO06K07+0T$03a0TY0Sy0OC0850Uk0c00LO09'0Co0FR0B#0R50kN0Cv0rW13s0S90Rx07K0AK05X07L0H306(0CQ0Ee0G90+*0740600OF0JB04*05*07h07i1Ta0kT0pV0ua0u'0mb0w10yk0690HT1tH04z08U0BH0LK0L[0TF0w70p20Kz1C]06%06a06P0B=02W0A{0B$1cr0Y(09R03p0S:0U90D@03^0Lp0GP04@0P208X0DU0Nz0Rj0Q&0P%0Q21$m0DT0g>0N/02q0s608r0Ep04=0B80Cw0A#07:0970y'0P90Sc04m03U0M90Hg0re09,0d+08^0hf0M%0Kv04,04W04n0g;15V1D/0+{0IE0IM0P:0u(0az0/N0ss0]70C!09E0Le0fC0=[0H>1z50BC0dk02U0B[05502$14?0Bq05@0A+0Qv05Y03@0IN0Bn0+v09I05(04Q04r06V05C1Bz08:07506n0G=0RP0OV0Pf0d@0XA0W'0lX0Kq0E[0R*04X0I)06q0+!0m/0jh04R05E04u0M^0F30H!0mB0G:0nD04T03M0E10D,02J0U=0Fq0Bh07103504[0og0jV0Ye1NR0X>0,<03I05O04A0CV0SK0WA0Z30HD06m0N%04M1Zg02,08/03807m04}0B20BD08*2Dd0{y0qq06B1BJ0Bw1KN0Bk0lC0P(0L00R90RT0X[0K10Wc07n0760mP0Hs0Ji0CD0$P0Ip06f03d0qT03k0=i08o0310EG03=02j0R30KX03D02*0][05,0:e0L>0A=0QB08h0Px08406^0P30T!1B%0l'0am0$70oA0Vv01}02k0C(0E=03&0Zo05$0OQ0yU0m40a?0l%0uz2hx01#06T08F09109V07T07M08;09x0A004L03q03V03n03B04U03N03b03:02%03C02<02>02n04i02602Y03602!05602=03A05%05z03l05P0J)0#%09307R0Oz0OD0R@0iw0Yv0Vl0W&04f0a(0O%0iN0&Z1it0V#0x30O[0220C202]02S0lQ01t01>01?02M02D02P05x01$01+0Nn0Ul04O04d05707w05M04P0XO06)0fv0cL1h#04:06&05c01M05}0I/0Jo0L10K40N30NX0Rn0ts0E?08K0FS07t07%04B0390D00Ek0CM0eb06!0G)06b0CH0CK1^304E02u05h0G40JK03*05/04109M02805<03t03!03<03v04]0AF0Aa0oH0fE0dN0Up0Ym0Xv0Oe0Vt1tR0gl0SW0Eq05)0KI03r0IZ0wS0hZ0V*05k0Br0Af03h0Lw08u0630]906#02a0D#09%0Fr0AB0Os0ZO0'O0ZU0X;0rb0R!0QV0aC0wd0T00x)0Q)0R<0Np0oB0n]0YD0c104Y0kG0AL0GL0GM0Fb0J*03;0cj0N=08a02(09e0Q<0BR0DI0Bl07j04D0BU06W0K%0wF0kC0?[0B908v0JD0KY1?]0P40a:0Gr1<90=706;0LM0BS09J08C0eJ05T0Y<0L60UZ10k0^v0p#0(A0DV0EM0370n31LN0GU0K(03(0860Me03206A04g05D0Mt01x06C02w08w0C+0eP04K02^03Q0Fk0Hm0N+0R[1yl05^0OZ0LA0JE0We0T40Pv0TO0sb1If0jd0If06*0Dy07}0730Wj08b0hP0Et04#03)0BK0b(08Q0tL0lr0kO0Ra04v0M]03w0d&07X09n0Cr0Py0Ao05n05F0Gi0PA04x03f0nh0i&0bP0ed0(G0jq0f)0rf0wZ0K:0W60A103'06/0Ov07S0K/06[03,02s02l0BV0C=0240E>08704C0HJ0AM02)0xH06r0640m50Ns0L;0HV0SP0f%08O0Y#0Q>0Wx0Mz0dK0O]0i+0Ch0MD0C608>03?01r02301/07y09S01&0Ht05H04h0km0X60i>02:05;09407b0RD02505y03>03S02z03H01*02/0O80Lo0Z904o0330Er0Rf0gx0LP0J&0Ql0LN0di0n'05I0I?0JA0}M0S^0O30LG05102I0Aj09504p03i0AN05U03[0U(0EE0FI0ha02B05803]0HA0Fu03W0Dj0K60RB0Bx02{07^05Z04$04/06602f07x04{07902Q05t04w03K05[0Yi0470G209C02h0G,0ao0iK0!k0l60cu0fJ0n$0OM0/<0MJ0P^0MR02o04;02}03g02'05&02?01=01q02T04306E02i06H07N03{02E04N01:02t0BW02e0C%0A805A0A'0HY0I20O:0Hh0PK0Ma0Wp0N40L,01[01(01@08802102m0KA0Ab0A)07p04802O03m05d06L01p02x01z0AY07209A04e0U40500DW02C02b02F05m06:03#07C06Q01N06v09}0N20G%0KB0K>0J>0eQ0W!03P02g03L02702K02X01j02r02Z02903e01^0N501;0Gn03Z06x01]0HQ01{01'06s04H0Tl02R0I;0Jp0L*0Ke0Kf0TQ03G01l02&05=02N0E;01,02+03}05V02#04G02y01%01<02[03X01w04Z04s01!02L01s0Mh0F40F#0F}0LR0G{0F[0Mv01f01v01o02G01k01m01y01Q01P01n01Y01h01O01d01W01e01a01X01b02001T01R01i01U01g01)01V01S01u01Z01c \ No newline at end of file diff --git a/Plain Craft Launcher 2/Application.xaml.vb b/Plain Craft Launcher 2/Application.xaml.vb index db89387..2b5e788 100644 --- a/Plain Craft Launcher 2/Application.xaml.vb +++ b/Plain Craft Launcher 2/Application.xaml.vb @@ -9,7 +9,10 @@ Public Class Application '核心初始化 MeloongCore.Main.Init("PCL") MeloongCore.Wpf.Main.Init() - Logger.Instance = New PclLogger With {.logFolder = Paths.BaseThenName, .MinLevel = If(ModeDebug, LogLevel.Trace, LogLevel.Info)} + If Not (e.Args.Contains("--update") OrElse e.Args.Contains("--gpu") OrElse e.Args.Contains("--memory")) Then '若是通过命令行调用,则不检查单例、不轮换日志(#9137) + WaitingMutex(e) + Logger.Instance = New PclLogger With {.logFolder = Paths.BaseThenName, .MinLevel = If(ModeDebug, LogLevel.Trace, LogLevel.Info)} + End If '提升主线程优先级 Thread.CurrentThread.Priority = ThreadPriority.Highest '执行开发版测试 @@ -47,7 +50,7 @@ Public Class Application Catch ex As Exception Environment.Exit(ProcessReturnValues.Fail) End Try - ElseIf e.Args(0).StartsWithF("--memory") Then + ElseIf e.Args(0) = "--memory" Then '内存优化 Dim Ram = My.Computer.Info.AvailablePhysicalMemory Try @@ -70,7 +73,7 @@ Public Class Application CheckPermissionWithException(Paths.Base & "PCL\") Catch ex As Exception MsgBox($"PCL 没有对当前文件夹的权限({Paths.Base}PCL\),请尝试:" & vbCrLf & - "1. 将 PCL 移动到其他文件夹" & If(Paths.Base.StartsWithF("C:", True), ",例如 C 盘和桌面以外的其他位置。", "。") & vbCrLf & + "1. 将 PCL 移动到其他文件夹" & If(PathUtils.GetDiskName(Paths.Base) = "C"c, ",例如 C 盘和桌面以外的其他位置。", "。") & vbCrLf & "2. 删除当前目录中的 PCL 文件夹,然后再试。" & vbCrLf & "3. 右键 PCL 选择属性,打开 兼容性 中的 以管理员身份运行此程序。", MsgBoxStyle.Critical, "运行环境错误") @@ -81,7 +84,7 @@ RetryCacheCheck: DirectoryUtils.Create(PathTemp) CheckPermissionWithException(PathTemp) Catch ex As Exception - If PathTemp = Path.GetTempPath() & "PCL\" Then + If PathUtils.IsSame(PathTemp, Path.GetTempPath() & "PCL\") Then MyMsgBox("PCL 无法访问缓存文件夹,可能导致程序出错或无法正常使用!" & vbCrLf & vbCrLf & "错误原因:" & ex.GetDisplay(True), "缓存文件夹不可用") Else MyMsgBox("手动设置的缓存文件夹不可用,PCL 将使用默认缓存文件夹。" & vbCrLf & vbCrLf & "错误原因:" & ex.GetDisplay(True), "缓存文件夹不可用") @@ -92,8 +95,6 @@ RetryCacheCheck: End Try DirectoryUtils.Create(PathTemp & "Cache\") DirectoryUtils.Create(Paths.AppDataThenName) - '要求单例 - WaitingMutex(e) '设置 ToolTipService 默认值 ToolTipService.InitialShowDelayProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(100)) '设置网络配置默认值 @@ -231,7 +232,7 @@ RetryCacheCheck: End Function End Sub Private Shared Sub ExtractLibwebp() '这个方法会调用 ModBase,进而调用 MeloongCore,所以不能放在 New 里 - SetDllDirectory(PathPure.Value.TrimEnd("\"c)) + SetDllDirectory(PathUtils.RemoveSlashSuffix(PathPure.Value)) Try ExtractResources(PathPure.Value & "libwebp.dll", "libwebp64") Catch ex As Exception diff --git a/Plain Craft Launcher 2/Controls/Behaviors/ClipboardInterceptor.vb b/Plain Craft Launcher 2/Controls/Behaviors/ClipboardInterceptor.vb index 6e594c8..ea83e13 100644 --- a/Plain Craft Launcher 2/Controls/Behaviors/ClipboardInterceptor.vb +++ b/Plain Craft Launcher 2/Controls/Behaviors/ClipboardInterceptor.vb @@ -44,8 +44,8 @@ Namespace Controls.Behaviors Private Shared Sub AddCommandBindingsToTextBox(tb As TextBox) tb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Copy, AddressOf OnCopyTextBox)) - tb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Cut, AddressOf OnCutTextBox)) - tb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Paste, AddressOf OnPasteTextBox)) + tb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Cut, AddressOf OnCutTextBox, AddressOf CanEditTextBox)) + tb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Paste, AddressOf OnPasteTextBox, AddressOf CanEditTextBox)) End Sub Private Shared Sub AddCommandBindingsToRichTextBox(rtb As RichTextBox) @@ -58,42 +58,61 @@ Namespace Controls.Behaviors dg.CommandBindings.Add(New CommandBinding(ApplicationCommands.Copy, AddressOf OnCopyDataGrid)) End Sub + Private Shared Sub SetClipboardText(text As String) + Forms.Clipboard.SetDataObject(text, False, retryTimes:=10, retryDelay:=100) '实际的复制 + Try + System.Windows.Clipboard.Flush() '将数据持久化到系统剪贴板,避免应用程序退出后数据丢失 + Catch ex As Exception '持久化失败不抛出异常 + Logger.Warn(ex, $"剪贴板持久化失败(错误码:0x{ex.HResult:X8})") + End Try + End Sub + Private Shared Sub OnCopyTextBox(sender As Object, e As ExecutedRoutedEventArgs) Dim tb = TryCast(sender, TextBox) If tb Is Nothing OrElse tb.SelectionLength <= 0 Then Return Try - Forms.Clipboard.Clear() - Forms.Clipboard.SetDataObject(tb.SelectedText, True) + SetClipboardText(tb.SelectedText) Catch ex As Exception - Logger.Warn(ex, "复制失败,请稍后再试", LogBehavior.Toast) + Logger.Warn(ex, $"复制失败,请稍后再试(错误码:0x{ex.HResult:X8})", LogBehavior.Toast) End Try e.Handled = True End Sub + Private Shared Sub CanEditTextBox(sender As Object, e As CanExecuteRoutedEventArgs) + Dim tb = TryCast(sender, TextBox) + e.CanExecute = tb IsNot Nothing AndAlso Not tb.IsReadOnly AndAlso + (e.Command IsNot ApplicationCommands.Cut OrElse tb.SelectionLength > 0) + e.Handled = True + End Sub + Private Shared Sub OnCutTextBox(sender As Object, e As ExecutedRoutedEventArgs) Dim tb = TryCast(sender, TextBox) - If tb Is Nothing OrElse tb.SelectionLength <= 0 Then Return + e.Handled = True + If tb Is Nothing OrElse tb.IsReadOnly OrElse tb.SelectionLength <= 0 Then Return Try - Forms.Clipboard.Clear() - Forms.Clipboard.SetDataObject(tb.SelectedText, True) + SetClipboardText(tb.SelectedText) Catch ex As Exception - Logger.Warn(ex, "剪切失败,请稍后再试", LogBehavior.Toast) + Logger.Warn(ex, $"剪切失败,请稍后再试(错误码:0x{ex.HResult:X8})", LogBehavior.Toast) + Return End Try tb.SelectedText = String.Empty - e.Handled = True End Sub Private Shared Sub OnPasteTextBox(sender As Object, e As ExecutedRoutedEventArgs) Dim tb = TryCast(sender, TextBox) - If tb Is Nothing Then Return + e.Handled = True + If tb Is Nothing OrElse tb.IsReadOnly Then Return Try - If Forms.Clipboard.ContainsText() Then - Dim pasteText = Forms.Clipboard.GetText() + Dim pasteText = Retrier.Attempt(func:= + Function(Attempt) As String + Return If(Forms.Clipboard.ContainsText(), Forms.Clipboard.GetText(), Nothing) + End Function, maxAttempts:=10, delay:=Function() TimeSpan.FromMilliseconds(100)) + If pasteText IsNot Nothing Then '将换行符替换为空格(#7825) If Not tb.AcceptsReturn Then pasteText = pasteText.ReplaceLineEndings(" ", mergeMultiple:=True) @@ -105,10 +124,8 @@ Namespace Controls.Behaviors tb.SelectionLength = 0 End If Catch ex As Exception - Logger.Warn(ex, "粘贴失败,请稍后再试", LogBehavior.Toast) + Logger.Warn(ex, $"粘贴失败,请稍后再试(错误码:0x{ex.HResult:X8})", LogBehavior.Toast) End Try - - e.Handled = True End Sub Private Shared Sub OnCopyRichTextBox(sender As Object, e As ExecutedRoutedEventArgs) @@ -119,10 +136,9 @@ Namespace Controls.Behaviors If String.IsNullOrEmpty(textRange.Text) Then Return Try - Forms.Clipboard.Clear() - Forms.Clipboard.SetDataObject(textRange.Text, True) + SetClipboardText(textRange.Text) Catch ex As Exception - Logger.Warn(ex, "复制失败,请稍后再试", LogBehavior.Toast) + Logger.Warn(ex, $"复制失败,请稍后再试(错误码:0x{ex.HResult:X8})", LogBehavior.Toast) End Try e.Handled = True @@ -134,16 +150,16 @@ Namespace Controls.Behaviors Dim selection = New TextRange(rtb.Selection.Start, rtb.Selection.End) If String.IsNullOrEmpty(selection.Text) Then Return + e.Handled = True Try - Forms.Clipboard.Clear() - Forms.Clipboard.SetDataObject(selection.Text, True) + SetClipboardText(selection.Text) Catch ex As Exception - Logger.Warn(ex, "剪切失败,请稍后再试", LogBehavior.Toast) + Logger.Warn(ex, $"剪切失败,请稍后再试(错误码:0x{ex.HResult:X8})", LogBehavior.Toast) + Return End Try selection.Text = String.Empty - e.Handled = True End Sub Private Shared Sub OnPasteRichTextBox(sender As Object, e As ExecutedRoutedEventArgs) @@ -151,9 +167,11 @@ Namespace Controls.Behaviors If rtb Is Nothing Then Return Try - If Not Forms.Clipboard.ContainsText() Then Return - - Dim pasteText = Forms.Clipboard.GetText() + Dim pasteText = Retrier.Attempt(func:= + Function(Attempt) As String + Return If(Forms.Clipboard.ContainsText(), Forms.Clipboard.GetText(), Nothing) + End Function, maxAttempts:=2, delay:=Function() TimeSpan.FromMilliseconds(200)) + If pasteText Is Nothing Then Return Dim selection = rtb.Selection selection.Text = pasteText @@ -162,7 +180,7 @@ Namespace Controls.Behaviors rtb.CaretPosition = caretPos rtb.Selection.Select(caretPos, caretPos) Catch ex As Exception - Logger.Warn(ex, "粘贴失败,请稍后再试", LogBehavior.Toast) + Logger.Warn(ex, $"粘贴失败,请稍后再试(错误码:0x{ex.HResult:X8})", LogBehavior.Toast) End Try e.Handled = True @@ -187,10 +205,9 @@ Namespace Controls.Behaviors Dim sbStr = sb.ToString().TrimEnd(ControlChars.Cr, ControlChars.Lf) Try - Forms.Clipboard.Clear() - Forms.Clipboard.SetDataObject(sbStr, True) + SetClipboardText(sbStr) Catch ex As Exception - Logger.Warn(ex, "复制失败,请稍后再试", LogBehavior.Toast) + Logger.Warn(ex, $"复制失败,请稍后再试(错误码:0x{ex.HResult:X8})", LogBehavior.Toast) End Try e.Handled = True diff --git a/Plain Craft Launcher 2/Controls/MyCard.vb b/Plain Craft Launcher 2/Controls/MyCard.vb index 08ced68..11d0a90 100644 --- a/Plain Craft Launcher 2/Controls/MyCard.vb +++ b/Plain Craft Launcher 2/Controls/MyCard.vb @@ -102,73 +102,36 @@ Public Class MyCard RunInUi(Sub() UseAnimation = RawUseAnimation, True) End If End Sub - Public Sub StackInstall() - StackInstall(SwapControl, SwapType, Title) - TriggerForceResize() + + '填充 + Private ContentBuilder As Action(Of Panel) + Public Sub SetContentBuilder(Builder As Action(Of Panel)) + ContentBuilder = Builder End Sub - Public Shared Sub StackInstall(ByRef Stack As StackPanel, Type As Integer, Optional CardTitle As String = "") - '这一部分的代码是好几年前留下的究极屎坑,当时还不知道该咋正确调用这种方法,就写了这么一坨屎 - '但是现在……反正勉强能用……懒得改了就这样吧.jpg - '别骂了别骂了.jpg - If Stack.Tag Is Nothing Then Return - '排序 - Dim HasDuplicates As Boolean = False - Select Case Type - Case 3 - Stack.Tag = CType(Stack.Tag, List(Of DlOptiFineListEntry)).SortByComparison(Function(a, b) CompareVersionGE(a.DisplayName, b.DisplayName)) - Case 4, 10 - Stack.Tag = CType(Stack.Tag, List(Of DlLiteLoaderListEntry)).SortByComparison(Function(a, b) CompareVersionGE(a.Inherit, b.Inherit)) - Case 6 - Stack.Tag = CType(Stack.Tag, List(Of DlForgeVersionEntry)).OrderByDescending(Function(a) a).ToList - Case 8, 9 - Stack.Tag = CType(Stack.Tag, List(Of ResourceVersion)).SortByComparison(Function(a, b) a.ReleaseDate > b.ReleaseDate) - HasDuplicates = - CType(Stack.Tag, List(Of ResourceVersion)).DistinctBy(Function(a) a.Display).Count <> - CType(Stack.Tag, List(Of ResourceVersion)).Count - End Select - '控件转换 - Select Case Type - Case 6 - ForgeDownloadListItemPreload(Stack, Stack.Tag, AddressOf ForgeSave_Click, True) - Case 8 - PageDownloadResourceDetail.ResourceFilesCardPreload(Stack, Stack.Tag) - End Select - '实现控件虚拟化 - For Each Data As Object In Stack.Tag - Select Case Type - Case 0 - Stack.Children.Add(CType(Data, McInstance).ToListItem(AddressOf PageSelectRight.McInstanceListContent)) - Case 2 - Stack.Children.Add(McDownloadListItem(Data, AddressOf McDownloadMenuSave, True)) - Case 3 - Stack.Children.Add(OptiFineDownloadListItem(Data, AddressOf OptiFineSave_Click, True)) - Case 6 - Stack.Children.Add(ForgeDownloadListItem(Data, AddressOf ForgeSave_Click, True)) - Case 7 - '不能使用 AddressOf,这导致了 #535,原因完全不明,疑似是编译器 Bug - Stack.Children.Add(McDownloadListItem(Data, Sub(sender, e) FrmDownloadInstall.MinecraftSelected(sender, e), False)) - Case 8 - '若存在重复的版本名,则显示文件名而非版本名(#1344) - Stack.Children.Add(CType(Data, ResourceVersion).ToListItem( - AddressOf FrmDownloadResourceDetail.Save_Click, IsBadDisplay:=HasDuplicates)) - Case 9 - '若存在重复的版本名,则显示文件名而非版本名(#1344) - Stack.Children.Add(CType(Data, ResourceVersion).ToListItem( - AddressOf FrmDownloadResourceDetail.Install_Click, AddressOf FrmDownloadResourceDetail.Save_Click, IsBadDisplay:=HasDuplicates)) - Case 10 - Stack.Children.Add(LiteLoaderDownloadListItem(Data, AddressOf LiteLoaderSave_Click, True)) - Case 11 - Stack.Children.Add(CType(Data, HelpEntry).ToListItem) - Case 12 - Stack.Children.Add(FabricDownloadListItem(CType(Data, JObject), AddressOf FrmDownloadInstall.Fabric_Selected)) - Case 13 - Stack.Children.Add(NeoForgeDownloadListItem(Data, AddressOf NeoForgeSave_Click, True)) - Case Else - Logger.Error($"未知的虚拟化种类:{Type}") - End Select - Next - Stack.Children.Add(New FrameworkElement With {.Height = 18}) '下边距,同时适应折叠 - Stack.Tag = Nothing + Public Sub SetContentBuilder(Of T)(Items As IEnumerable(Of T), ItemBuilder As Func(Of T, UIElement)) + SetContentBuilder( + Sub(Stack) + For Each Item In Items + Stack.Children.Add(ItemBuilder(Item)) + Next + Stack.Children.Add(New FrameworkElement With {.Height = 18}) + End Sub) + End Sub + ''' + ''' 使用 ContentBuilder 生成卡片内容。 + ''' + Public Sub BuildContent() + If ContentBuilder Is Nothing Then Return + Dim Stack = TryCast(SwapControl, Panel) + If Stack Is Nothing Then Return + Try + ContentBuilder(Stack) + ContentBuilder = Nothing + Catch + Stack.Children.Clear() + Throw + End Try + TriggerForceResize() End Sub '动画 @@ -278,10 +241,6 @@ Public Class MyCard Public SwapControl As FrameworkElement Public Property CanSwap As Boolean = False ''' - ''' 被折叠的种类,用于控件虚拟化。 - ''' - Public Property SwapType As Integer - ''' ''' 是否已被折叠。 ''' Public Property IsSwapped As Boolean @@ -293,7 +252,7 @@ Public Class MyCard _IsSwapped = value If SwapControl Is Nothing Then Return '展开 - If Not IsSwapped AndAlso TypeOf SwapControl Is StackPanel Then StackInstall(SwapControl, SwapType, Title) + If Not IsSwapped Then BuildContent() '若尚未加载,会在 Loaded 事件中触发无动画的折叠,不需要在这里进行 If Not IsLoaded Then Return '更新样式 @@ -397,4 +356,4 @@ Partial Public Module ModAnimation If CallBack IsNot Nothing Then CallBack(Control) End If End Sub -End Module \ No newline at end of file +End Module diff --git a/Plain Craft Launcher 2/Controls/MyImage.vb b/Plain Craft Launcher 2/Controls/MyImage.vb index a7784ef..b385ea7 100644 --- a/Plain Craft Launcher 2/Controls/MyImage.vb +++ b/Plain Craft Launcher 2/Controls/MyImage.vb @@ -90,7 +90,7 @@ Public Class MyImage Catch ex As Exception Logger.Warn(ex, $"加载图片失败({value})") Try - If value.StartsWithF(PathTemp) Then FileUtils.Delete(value) + If value.StartsWithF(PathTemp, True) Then FileUtils.Delete(value) Catch End Try End Try diff --git a/Plain Craft Launcher 2/FormMain.xaml.vb b/Plain Craft Launcher 2/FormMain.xaml.vb index 49e51e8..902abdb 100644 --- a/Plain Craft Launcher 2/FormMain.xaml.vb +++ b/Plain Craft Launcher 2/FormMain.xaml.vb @@ -11,6 +11,13 @@ Public Class FormMain Dim FeatureList As New List(Of KeyValuePair(Of Integer, String)) '统计更新日志条目 If BuildType = BuildTypes.Release Then + If LastVersion < 411 Then 'Release 2.14.0.0 + FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "新增:允许修改、添加、移除、更新已安装版本的 MC 版本以及各个组件,可从版本设置中进入")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:启动游戏时允许按 Esc 取消")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:允许复制主页的启动日志中的内容")) + FeatureCount += 19 + BugCount += 26 + End If If LastVersion < 408 Then 'Release 2.13.1.1 FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:版本设置中,添加打开截图文件夹的快捷方式")) FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:导出整合包时,可以勾选导出光影设置文件")) @@ -93,6 +100,13 @@ Public Class FormMain '3:BUG+ IMP* FEAT- '2:BUG* IMP- '1:BUG- + If LastVersion < 412 Then 'Snapshot 2.14.0.0 + FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "新增:允许修改、添加、移除、更新已安装版本的 MC 版本以及各个组件,可从版本设置中进入")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:启动游戏时允许按 Esc 取消")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:允许复制主页的启动日志中的内容")) + FeatureCount += 19 + BugCount += 26 + End If If LastVersion < 410 Then 'Snapshot 2.13.1.1 If LastVersion = 409 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:更新 Mod 时可能更新成另一个 Mod 加载器的版本")) FeatureCount += 2 @@ -786,7 +800,7 @@ Public Class FormMain If PanMsg.Children.Any Then If e.Key = Key.Enter Then CType(PanMsg.Children(0), Object).Btn1_Click() - ElseIf e.Key = Key.Escape OrElse (e.Key = Key.W AndAlso Keyboard.Modifiers.HasFlag(ModifierKeys.Control)) Then + ElseIf e.Key = Key.Escape Then Dim Msg As Object = PanMsg.Children(0) If TypeOf Msg IsNot MyMsgInput AndAlso TypeOf Msg IsNot MyMsgSelect AndAlso Msg.Btn3.Visibility = Visibility.Visible Then Msg.Btn3_Click() @@ -803,13 +817,13 @@ Public Class FormMain ' 在没有弹窗时:继续检查…… '========================== - '按 ESC:启动中则取消启动,否则返回上一级 + '按 ESC 取消启动,或返回上一级 If e.Key = Key.Escape Then - If McLaunchLoader.State = LoadState.Loading Then - McLaunchCancel() - Else - TriggerPageBack() + If PageCurrent = FormMain.PageType.Launch AndAlso McLaunchLoader.State = LoadState.Loading Then + FrmLaunchLeft.BtnCancel_Click() + Return End If + TriggerPageBack() End If '更改隐藏版本可见性 If e.Key = Key.F11 AndAlso PageCurrent = FormMain.PageType.InstanceSelect Then @@ -834,11 +848,6 @@ Public Class FormMain If TypeOf PageRight Is IRefreshable Then CType(PageRight, IRefreshable).Refresh() Return End If - 'Ctrl + w 关闭程序 - If e.Key = Key.W AndAlso Keyboard.Modifiers.HasFlag(ModifierKeys.Control) Then - EndProgram(True) - Return - End If '调用启动游戏 If e.Key = Key.Enter AndAlso PageCurrent = FormMain.PageType.Launch Then If IsAprilEnabled AndAlso Not IsAprilGiveup Then @@ -1026,14 +1035,14 @@ Public Class FormMain If FilePathList.Count > 1 Then '必须要求全部为 jar 文件 For Each File In PathList - If Not {"jar", "litemod", "disabled", "old"}.Contains(File.AfterLast(".").Lower) Then + If Not {"jar", "litemod", "disabled", "old"}.Contains(PathUtils.GetExtension(File)) Then Hint("一次请只拖入一个文件!", HintType.Red) Return End If Next End If '主页 - Dim Extension As String = FilePath.AfterLast(".").Lower + Dim Extension As String = PathUtils.GetExtension(FilePath) If Extension = "xaml" Then Logger.Info("文件后缀为 XAML,作为主页加载") If FileUtils.Exists(Paths.Base & "PCL\Custom.xaml") Then @@ -1054,9 +1063,9 @@ Public Class FormMain If PageInstanceMod.InstallMods(PathList) Then Return '安装整合包 If {"zip", "rar", "mrpack"}.Any(Function(t) t = Extension) Then '部分压缩包是 zip 格式但后缀为 rar,总之试一试 - Logger.Info("文件为压缩包,尝试作为整合包安装") - Hint("检测到压缩包拖入,正在准备安装……") + Hint("正在读取压缩包……") Try + Logger.Info("文件为压缩包,尝试作为整合包安装") ModpackInstall(FilePath) RunInUi(Sub() FrmMain.PageChange(FormMain.PageType.TaskManager)) Return @@ -1067,7 +1076,7 @@ Public Class FormMain End If 'RAR 处理 If Extension = "rar" Then - Hint("PCL 无法处理 rar 格式的压缩包,请在解压后重新压缩为 zip 格式再试!") + Hint("PCL 无法处理 rar 格式的压缩包,请在解压后重新压缩为 zip 格式再试!", HintType.Red) Return End If '错误报告分析 @@ -1083,7 +1092,7 @@ Public Class FormMain Logger.Error(ex, "自主错误报告分析失败") End Try '未知操作 - Hint("PCL 无法确定应当执行的文件拖拽操作……") + Hint("PCL 无法确定适合的操作……", HintType.Red) End Sub, "文件拖拽") End Sub @@ -1225,9 +1234,10 @@ Public Class FormMain OtherTest = 2 InstanceOverall = 0 InstanceSetup = 1 - InstanceMod = 2 - InstanceModDisabled = 3 - InstanceExport = 4 + InstanceModify = 2 + InstanceMod = 3 + InstanceModDisabled = 4 + InstanceExport = 5 End Enum ''' ''' 获取次级页面的名称。若并非次级页面则返回空字符串,故可以以此判断是否为次级页面。 @@ -1392,6 +1402,10 @@ Public Class FormMain ''' Private Sub PageChangeActual(Stack As PageStackData, Optional SubType As PageSubType = -1) If PageCurrent = Stack AndAlso (PageCurrentSub = SubType OrElse SubType = -1) Then Return + If Stack.Page = PageType.InstanceSetup AndAlso PageInstanceLeft.Instance IsNot Nothing AndAlso McInstanceLock.IsLocked(PageInstanceLeft.Instance) Then + Hint("该版本正在被修改,暂时无法进入版本设置!", HintType.Red) + Return + End If AniControlEnabled += 1 Try diff --git a/Plain Craft Launcher 2/Modules/Base/ModBase.vb b/Plain Craft Launcher 2/Modules/Base/ModBase.vb index c591cc6..6a89d7f 100644 --- a/Plain Craft Launcher 2/Modules/Base/ModBase.vb +++ b/Plain Craft Launcher 2/Modules/Base/ModBase.vb @@ -6,12 +6,12 @@ Public Module ModBase #Region "声明" '下列版本信息由更新器自动修改 - Public Const VersionBaseName As String = "2.13.1.1" '显示用版本名 + Public Const VersionBaseName As String = "2.14.0.0" '显示用版本名 Public Const CommitHash As String = "" 'Commit Hash,由 GitHub Workflow 自动替换 #If RELEASE Then - Public Const VersionCode As Integer = 408 '正式版 + Public Const VersionCode As Integer = 411 '正式版 #Else - Public Const VersionCode As Integer = 410 '快照版 + Public Const VersionCode As Integer = 412 '快照版 #End If '版本信息 @@ -47,7 +47,7 @@ Public Module ModBase ''' ''' 程序的缓存文件夹路径,以 \ 结尾。 ''' - Public PathTemp As String = If(Settings.Get(Of String)("SystemSystemCache") = "", Path.GetTempPath() & "PCL\", Settings.Get(Of String)("SystemSystemCache")).ToString.Replace("/", "\").TrimEnd("\") & "\" + Public PathTemp As String = PathUtils.AddSlashSuffix(If(Settings.Get(Of String)("SystemSystemCache") = "", Path.GetTempPath() & "PCL\", Settings.Get(Of String)("SystemSystemCache")).Replace("/", "\")) ''' ''' 当前程序的语言。 ''' @@ -75,7 +75,7 @@ Public Module ModBase ''' ''' 系统盘盘符,以 \ 结尾。例如 “C:\”。 ''' - Public OsDrive As String = Environment.GetLogicalDrives().Where(Function(p) Directory.Exists(p)).First.Upper.First & ":\" '#3799 + Public OsDrive As String = Environment.GetLogicalDrives().First(Function(p) Directory.Exists(p)) '#3799 Public ReadOnly Property ModeDebug As Boolean Get @@ -576,7 +576,7 @@ Public Module ModBase Try If String.IsNullOrEmpty(Folder) Then Return False Folder = PathUtils.AddSlashSuffix(Folder) - If Folder.EndsWithF(":\System Volume Information\") OrElse Folder.EndsWithF(":\$RECYCLE.BIN\") Then Return False + If Folder.EndsWithF(":\System Volume Information\", True) OrElse Folder.EndsWithF(":\$RECYCLE.BIN\", True) Then Return False If Not DirectoryUtils.Exists(Folder) Then Return False Dim TestFilePath As String = $"{Folder}CheckPermission{GetUuid()}.txt" FileUtils.Write(TestFilePath, "临时文件,用于检测该文件夹的权限是否正常。") @@ -593,7 +593,7 @@ Public Module ModBase Public Sub CheckPermissionWithException(Folder As String) If String.IsNullOrWhiteSpace(Folder) Then Throw New ArgumentNullException("文件夹名不能为空!") Folder = PathUtils.AddSlashSuffix(Folder) - If Folder.EndsWithF(":\System Volume Information\") OrElse Folder.EndsWithF(":\$RECYCLE.BIN\") Then Throw New UnauthorizedAccessException("没有对系统文件夹的权限!") + If Folder.EndsWithF(":\System Volume Information\", True) OrElse Folder.EndsWithF(":\$RECYCLE.BIN\", True) Then Throw New UnauthorizedAccessException("没有对系统文件夹的权限!") If Not DirectoryUtils.Exists(Folder) Then Throw New DirectoryNotFoundException("文件夹不存在!") Dim TestFilePath As String = $"{Folder}CheckPermission{GetUuid()}.txt" FileUtils.Write(TestFilePath, "临时文件,用于检测该文件夹的权限是否正常。") @@ -788,7 +788,7 @@ Public Module ModBase ''' ''' 可用于临时存放文件的,不含任何特殊字符的文件夹路径,以“\”结尾。 ''' - Public PathPure As Lazy(Of String) = New Lazy(Of String)( + Public PathPure As New Lazy(Of String)( Function() If Paths.Base.IsAsciiOnly() Then Return Paths.Base & "PCL\" @@ -845,6 +845,12 @@ Public Module ModBase Return (Now.ToUniversalTime().Ticks - 621355968000000000) / 10000000 End Function ''' + ''' 获取十进制 Unix 时间戳。 + ''' + Public Function ToUnixTimestampUtc(Time As Date) As Long + Return (Time.ToUniversalTime().Ticks - 621355968000000000) / 10000000 + End Function + ''' ''' 时间戳转化为日期。 ''' Public Function GetDate(timeStamp As Integer) As Date @@ -1021,20 +1027,22 @@ Public Module ModBase ''' ''' 设置剪贴板。将在另一线程运行,且不会抛出异常。 ''' - Public Sub ClipboardSet(Text As String, Optional ShowSuccessHint As Boolean = True, Optional SuccessHint As String = "") + Public Sub ClipboardSet(Text As String, Optional ShowSuccessHint As Boolean = True) RunInThread( Sub() - RunInUi( - Sub() - Try - My.Computer.Clipboard.Clear() - If Not String.IsNullOrEmpty(Text) Then My.Computer.Clipboard.SetText(Text) - If ShowSuccessHint Then Hint(If(String.IsNullOrEmpty(SuccessHint), "已成功复制!", SuccessHint), HintType.Green) - Catch ex As Exception - Logger.Error(ex, behavior:=LogBehavior.None) - Hint("复制失败,可能由于剪贴板被其他程序占用!", HintType.Red) - End Try - End Sub) + Try + Retrier.Attempt(delay:=Function(Attempt) TimeSpan.FromMilliseconds(200), maxAttempts:=4, isRetryAllowed:=Function(ex) True, action:= + Sub() + RunInUiWait( + Sub() + My.Computer.Clipboard.Clear() + If Not String.IsNullOrEmpty(Text) Then My.Computer.Clipboard.SetText(Text) + End Sub) + End Sub) + If ShowSuccessHint Then Hint("已成功复制!", HintType.Green) + Catch ex As Exception + Logger.Error(ex, "可能由于剪贴板被其他程序占用,文本复制失败", LogBehavior.Toast) + End Try End Sub) End Sub ''' diff --git a/Plain Craft Launcher 2/Modules/Base/ModNet.vb b/Plain Craft Launcher 2/Modules/Base/ModNet.vb index 34c994e..36b39ce 100644 --- a/Plain Craft Launcher 2/Modules/Base/ModNet.vb +++ b/Plain Craft Launcher 2/Modules/Base/ModNet.vb @@ -1396,7 +1396,7 @@ FinishExCatch: Public Overrides Sub Start(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = False) If Input IsNot Nothing Then Files = New ConcurrentQueue(Of NetFile)(CType(Input, IEnumerable(Of NetFile))) '去重 - Files = New ConcurrentQueue(Of NetFile)(Files.DistinctBy(Function(a) a.LocalPath)) + Files = New ConcurrentQueue(Of NetFile)(Files.DistinctBy(Function(a) PathUtils.ForCompare(a.LocalPath).Lower())) '设置剩余文件数 SyncLock FileRemainLock FileRemain += Files.Where(Function(f) f.State <> NetState.Finished).Count @@ -1421,7 +1421,7 @@ FinishExCatch: Next If Not File.HasAvailableSource() Then Throw New ArgumentException("输入的下载链接不正确!") File.LocalPath = File.LocalPath.Replace("/", "\") - If Not File.LocalPath.Lower.Contains(":\") Then Throw New ArgumentException("输入的本地文件地址不正确:" & File.LocalPath) + If Not File.LocalPath.Contains(":\") Then Throw New ArgumentException("输入的本地文件地址不正确:" & File.LocalPath) If File.LocalPath.EndsWithF("\") Then Throw New ArgumentException("请输入含文件名的完整文件路径:" & File.LocalPath) DirectoryUtils.Create(PathUtils.RemoveLastPart(File.LocalPath)) '创建目标文件夹 Next @@ -1519,7 +1519,7 @@ FinishExCatch: Select(Function(FolderName) (FolderName, File.LocalPath.IndexOfF(FolderName, True))). Where(Function(kv) kv.Item2 >= 0).ToList If Not TypeIndexes.Any Then - If File.LocalName.EndsWithF(".jar") Then + If PathUtils.GetExtension(File.LocalName) = "jar" Then TypeIndexes.Add(("\versions\", 1)) '总是对 jar 进行版本文件检查,以包括另存为 jar 的情况 Else Return Nothing @@ -1547,7 +1547,7 @@ FinishExCatch: Case "versions\" '版本 jar 或 json:查找 MC 文件夹下的各个版本文件夹 For Each VersionFolder In VersionFolders - For Each Candidate In DirectoryUtils.EnumerateFiles(VersionFolder, searchPattern:="*." & PathUtils.GetLastPart(File.LocalPath).AfterLast(".").Lower) + For Each Candidate In DirectoryUtils.EnumerateFiles(VersionFolder, searchPattern:="*." & PathUtils.GetExtension(File.LocalPath)) If CheckCandidate(Candidate, File.Check) Then Return Candidate Next Next diff --git a/Plain Craft Launcher 2/Modules/Base/ModValidate.vb b/Plain Craft Launcher 2/Modules/Base/ModValidate.vb index d24cc90..7122390 100644 --- a/Plain Craft Launcher 2/Modules/Base/ModValidate.vb +++ b/Plain Craft Launcher 2/Modules/Base/ModValidate.vb @@ -382,7 +382,7 @@ Public Class ValidateFolderPath Public Overrides Function Validate(Str As String) As String '去除尾部斜线,统一为 \ Str = Str.Replace("/", "\") - If Not Str.TrimEnd("\").EndsWithF(":") Then Str = Str.TrimEnd("\") + If Not PathUtils.RemoveSlashSuffix(Str).EndsWithF(":") Then Str = PathUtils.RemoveSlashSuffix(Str) '检查是否为空 Dim LengthCheck As String = New ValidateNullOrWhiteSpace().Validate(Str) If LengthCheck <> "" Then Return LengthCheck diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb index 4b30a0b..ba029b1 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb @@ -92,7 +92,7 @@ Public Class CrashAnalyzer '尝试视作压缩包解压 Try Dim Info = FileUtils.GetInfo(FilePath) - If Info.Exists AndAlso Info.Length > 0 AndAlso Not FilePath.EndsWithF(".jar", True) Then + If Info.Exists AndAlso Info.Length > 0 AndAlso Not PathUtils.GetExtension(FilePath) = "jar" Then FileUtils.ExtractToDirectory(FilePath, TempFolder & "Temp\") Logger.Info($"已解压导入的日志文件:{FilePath}") GoTo Extracted @@ -163,9 +163,9 @@ Extracted: Else TargetType = AnalyzeFileType.ExtraLogFile End If - ElseIf MatchName.EndsWithF(".log", True) Then + ElseIf PathUtils.GetExtension(MatchName) = "log" Then TargetType = AnalyzeFileType.ExtraLogFile - ElseIf MatchName.EndsWithF(".txt", True) Then + ElseIf PathUtils.GetExtension(MatchName) = "txt" Then TargetType = AnalyzeFileType.ExtraReportFile Else Logger.Info($"{MatchName} 分类为 Ignore") diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb index 67490af..ce90887 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb @@ -242,8 +242,6 @@ Public Module ModDownload DlPreferMojang = DeltaTime < 4000 Logger.Info($"Mojang 官方源加载耗时:{DeltaTime}ms,{If(DlPreferMojang, "可优先使用官方源", "不优先使用官方源")}") End If - '添加 PCL 特供项 - If FileUtils.Exists(PathTemp & "Cache\download.json") Then Versions.Merge(FileUtils.ReadAsJson(PathTemp & "Cache\download.json")) '返回 Loader.Output = New DlClientListResult With {.IsOfficial = True, .SourceName = "Mojang 官方源", .Value = Json} 'MC 更新提示 @@ -438,7 +436,7 @@ Public Module ModDownload .FileName = If(Name(i).ContainsIgnoreCase("pre"), "preview_", "") & "OptiFine_" & Name(i).Replace(" ", "_") & ".jar", .RequiredForgeVersion = Forge(i).Replace("Forge ", "").Replace("#", "")} If Entry.RequiredForgeVersion.Contains("N/A") Then Entry.RequiredForgeVersion = Nothing - Entry.InstanceName = Entry.Inherit & "-OptiFine_" & Name(i).ToString.Replace(" ", "_").Replace(Entry.Inherit & "_", "") + Entry.InstanceName = Entry.Inherit & "-OptiFine_" & Name(i).AfterFirst(" ").Replace(" ", "_") Versions.Add(Entry) Next Loader.Output = New DlOptiFineListResult With {.IsOfficial = True, .SourceName = "OptiFine 官方源", .Value = Versions} @@ -581,7 +579,7 @@ Public Module ModDownload If Version <> other.Version Then Return Version.CompareTo(other.Version) Else - Return CompareVersion(VersionName, other.VersionName) + Return McUtils.CompareVersion(VersionName, other.VersionName) End If End Function End Class diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb index 7ce900d..20ef01b 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb @@ -18,7 +18,7 @@ Public Module ModJava '强制选择的 Java Dim SelectedJavaSetting = Settings.Get(Of String)("LaunchArgumentJavaSelect") Dim SelectedJava = If(SelectedJavaSetting.StartsWithF("{"), SelectedJavaSetting.DeserializeJson(Of JObject)()("Path").ToString, "") - Dim Match = OldJavaList.FirstOrDefault(Function(j) j.Folder.Equals(SelectedJava, StringComparison.OrdinalIgnoreCase)) + Dim Match = OldJavaList.FirstOrDefault(Function(j) PathUtils.IsSame(j.Folder, SelectedJava)) If Match IsNot Nothing Then OldJavaList.Remove(Match) OldJavaList.Insert(0, Match) @@ -78,7 +78,7 @@ Public Module ModJava Return End If '不再移除它 - Configs.JavaRemovedList.Edit(Sub(ByRef List) List.RemoveIf(Function(f) f.Equals(NewJava.Folder, StringComparison.OrdinalIgnoreCase))) + Configs.JavaRemovedList.Edit(Sub(ByRef List) List.RemoveIf(Function(f) PathUtils.IsSame(f, NewJava.Folder))) '加入列表顶部 Configs.JavaList.Edit( Sub(ByRef List) @@ -150,16 +150,16 @@ Public Module ModJava End Sub '原版 26-:通过版本号判断 - If (Not Instance.Version.Vaild AndAlso Instance.ReleaseTime >= New Date(2024, 4, 2)) OrElse - (Instance.Version.Vaild AndAlso Instance.Version.Vanilla >= New Version(20, 0, 5)) Then + If (Not Instance.Version.Valid AndAlso Instance.ReleaseTime >= New Date(2024, 4, 2)) OrElse + (Instance.Version.Valid AndAlso Instance.Version.Vanilla >= New Version(20, 0, 5)) Then '1.20.5+(24w14a+):至少 Java 21 AddConstraint(ValueRange(Of Version).AtLeast(New Version(21, 0))) - ElseIf (Not Instance.Version.Vaild AndAlso Instance.ReleaseTime >= New Date(2021, 11, 16)) OrElse - (Instance.Version.Vaild AndAlso Instance.Version.Vanilla.Major >= 18) Then + ElseIf (Not Instance.Version.Valid AndAlso Instance.ReleaseTime >= New Date(2021, 11, 16)) OrElse + (Instance.Version.Valid AndAlso Instance.Version.Vanilla.Major >= 18) Then '1.18 pre2+:至少 Java 17 AddConstraint(ValueRange(Of Version).AtLeast(New Version(17, 0))) - ElseIf (Not Instance.Version.Vaild AndAlso Instance.ReleaseTime >= New Date(2021, 5, 11)) OrElse - (Instance.Version.Vaild AndAlso Instance.Version.Vanilla.Major >= 17) Then + ElseIf (Not Instance.Version.Valid AndAlso Instance.ReleaseTime >= New Date(2021, 5, 11)) OrElse + (Instance.Version.Valid AndAlso Instance.Version.Vanilla.Major >= 17) Then '1.17+ (21w19a+):至少 Java 16 AddConstraint(ValueRange(Of Version).AtLeast(New Version(16, 0))) ElseIf Instance.ReleaseTime.Year >= 2017 Then 'Minecraft 1.12 与 1.11 的分界线正好是 2017 年,太棒了 @@ -185,7 +185,7 @@ Public Module ModJava End If 'OptiFine 检测 - If Instance.Version.HasOptiFine AndAlso Instance.Version.Vaild Then '不管非标准版本 + If Instance.Version.HasOptiFine AndAlso Instance.Version.Valid Then '不管非标准版本 If Instance.Version.Vanilla.Major < 7 Then '<1.7:至多 Java 8 AddConstraint(ValueRange(Of Version).LessThan(New Version(9, 0))) @@ -199,11 +199,11 @@ Public Module ModJava End If 'Forge 检测(部分测试结果见 #8432) - If Instance.Version.HasForge Then + If Instance.Version.ModLoaders.HasFlagF(ModLoaders.Forge) Then If Instance.Version.Vanilla >= New Version(6, 0, 1) AndAlso Instance.Version.Vanilla <= New Version(7, 0, 2) Then '1.6.1 - 1.7.2:必须 Java 7 AddConstraint(ValueRange(Of Version).ClosedOpen(New Version(7, 0), New Version(8, 0))) - ElseIf Instance.Version.Vanilla.Major <= 12 OrElse Not Instance.Version.Vaild Then '非标准版本 + ElseIf Instance.Version.Vanilla.Major <= 12 OrElse Not Instance.Version.Valid Then '非标准版本 '<=1.12:Java 8 AddConstraint(ValueRange(Of Version).LessThan(New Version(9, 0))) ElseIf Instance.Version.Vanilla.Major <= 14 Then @@ -212,38 +212,38 @@ Public Module ModJava ElseIf Instance.Version.Vanilla.Major = 15 Then '1.15:Java 8 - 15 AddConstraint(ValueRange(Of Version).ClosedOpen(New Version(8, 0), New Version(16, 0))) - ElseIf CompareVersionGE(Instance.Version.Forge, "34.0.0") AndAlso CompareVersionGE("36.2.25", Instance.Version.Forge) Then + ElseIf McUtils.CompareVersionGE(Instance.Version.ModLoaderVersions(ModLoaders.Forge), "34.0.0") AndAlso McUtils.CompareVersionGE("36.2.25", Instance.Version.ModLoaderVersions(ModLoaders.Forge)) Then '1.16.3~5,Forge 34.0.0 ~ 36.2.25:最高 Java 8u320 AddConstraint(ValueRange(Of Version).AtMost(New Version(8, 0, 320))) - ElseIf CompareVersionGE(Instance.Version.Forge, "36.2.26") AndAlso CompareVersionGE("36.999999.999999", Instance.Version.Forge) Then + ElseIf McUtils.CompareVersionGE(Instance.Version.ModLoaderVersions(ModLoaders.Forge), "36.2.26") AndAlso McUtils.CompareVersionGE("36.999999.999999", Instance.Version.ModLoaderVersions(ModLoaders.Forge)) Then '1.16.5,Forge 36.2.26+,<37.0.0:最高 Java 23 AddConstraint(ValueRange(Of Version).LessThan(New Version(24, 0))) - ElseIf CompareVersionGE(Instance.Version.Forge, "37.0.0") AndAlso CompareVersionGE("37.0.79", Instance.Version.Forge) Then + ElseIf McUtils.CompareVersionGE(Instance.Version.ModLoaderVersions(ModLoaders.Forge), "37.0.0") AndAlso McUtils.CompareVersionGE("37.0.79", Instance.Version.ModLoaderVersions(ModLoaders.Forge)) Then '1.17.1,Forge 37.0.0 ~ 37.0.79:最高 Java 16 AddConstraint(ValueRange(Of Version).LessThan(New Version(17, 0))) ElseIf Instance.Version.Vanilla.Major = 18 AndAlso Instance.Version.HasOptiFine Then '#305 '1.18:若安装了 OptiFine,最高 Java 18 AddConstraint(ValueRange(Of Version).LessThan(New Version(19, 0))) - ElseIf CompareVersionGE(Instance.Version.Forge, "45.0.21") AndAlso CompareVersionGE("45.0.65", Instance.Version.Forge) Then + ElseIf McUtils.CompareVersionGE(Instance.Version.ModLoaderVersions(ModLoaders.Forge), "45.0.21") AndAlso McUtils.CompareVersionGE("45.0.65", Instance.Version.ModLoaderVersions(ModLoaders.Forge)) Then '1.19.4,Forge 45.0.21 ~ 45.0.65:最高 Java 19 AddConstraint(ValueRange(Of Version).LessThan(New Version(20, 0))) - ElseIf CompareVersionGE(Instance.Version.Forge, "45.0.66") AndAlso CompareVersionGE("47.4.8", Instance.Version.Forge) Then + ElseIf McUtils.CompareVersionGE(Instance.Version.ModLoaderVersions(ModLoaders.Forge), "45.0.66") AndAlso McUtils.CompareVersionGE("47.4.8", Instance.Version.ModLoaderVersions(ModLoaders.Forge)) Then '1.19.4~1.20.1,Forge 45.0.66 ~ 47.4.8:最高 Java 21 AddConstraint(ValueRange(Of Version).LessThan(New Version(22, 0))) End If End If 'NeoForge 检测 - If Instance.Version.HasNeoForge Then + If Instance.Version.ModLoaders.HasFlagF(ModLoaders.NeoForge) Then If Instance.Version.Vanilla = New Version(20, 0, 1) OrElse - (CompareVersionGE("20.2.62-beta", Instance.Version.NeoForge) AndAlso Not Instance.Version.NeoForge.Contains("25w14craftmine")) Then + (McUtils.CompareVersionGE("20.2.62-beta", Instance.Version.ModLoaderVersions(ModLoaders.NeoForge)) AndAlso Not Instance.Version.ModLoaderVersions(ModLoaders.NeoForge).Contains("25w14craftmine")) Then '1.20.1,以及 1.20.2 的 20.2.62-beta 之前:最高 Java 21 AddConstraint(ValueRange(Of Version).LessThan(New Version(22, 0))) End If End If 'Fabric 检测 - If Instance.Version.HasFabric AndAlso Instance.Version.Vaild Then '不管非标准版本 + If Instance.Version.ModLoaders.HasFlagF(ModLoaders.Fabric) AndAlso Instance.Version.Valid Then '不管非标准版本 If Instance.Version.Vanilla.Major >= 15 AndAlso Instance.Version.Vanilla.Major <= 16 Then '1.15 - 1.16:Java 8+ AddConstraint(ValueRange(Of Version).AtLeast(New Version(8, 0))) @@ -251,15 +251,14 @@ Public Module ModJava '1.18+:Java 17+ AddConstraint(ValueRange(Of Version).AtLeast(New Version(17, 0))) End If - If Instance.Version.Fabric <> "未知版本" AndAlso - CompareVersion(Instance.Version.Fabric, "0.17.0") < 0 Then + If Instance.Version.ModLoaderVersions(ModLoaders.Fabric) <> "未知版本" AndAlso McUtils.CompareVersion(Instance.Version.ModLoaderVersions(ModLoaders.Fabric), "0.17.0") < 0 Then 'Fabric Loader 0.16.x 及更早版本的 Mixin/ASM 不兼容 Java 25 AddConstraint(ValueRange(Of Version).LessThan(New Version(25, 0))) End If End If 'LiteLoader 检测 - If Instance.Version.HasLiteLoader AndAlso Instance.Version.Vaild Then '不管非标准版本 + If Instance.Version.ModLoaders.HasFlagF(ModLoaders.LiteLoader) AndAlso Instance.Version.Valid Then '不管非标准版本 '最高 Java 8 AddConstraint(ValueRange(Of Version).LessThan(New Version(9, 0))) End If @@ -411,9 +410,9 @@ Public Module ModJava Dim Components = DownloadListWorker.LastResult '确定要下载的目标 Java - Dim JavaToDownload As JavaDownloadInfo = If( - Components.FirstOrDefault(Function(Comp) Comp.Java("version")("name").ToString = RecommendedComponent), '优先选择指定的 Component - Components.FirstOrDefault(Function(Comp) Range.Contains(Comp.JavaVersion))) + Dim JavaToDownload As JavaDownloadInfo = Components.FirstOrDefault( + Function(Comp) Comp.Java("version")("name").ToString = RecommendedComponent, '优先选择指定的 Component + Function(Comp) Range.Contains(Comp.JavaVersion)) If JavaToDownload Is Nothing Then Logger.Warn($"Mojang 提供的 Java 列表中没有合适的 Java,无法自动下载") '提示需要手动下载 Java diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb index 71bedf7..504cd96 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb @@ -48,7 +48,7 @@ Public Module ModLaunch End Try '检查版本 CurrentLaunchOptions.Instance.Load() - If CurrentLaunchOptions.Instance.State = McInstanceState.Error Then + If CurrentLaunchOptions.Instance.State = McInstance.McInstanceState.Error Then Hint($"无法启动 {CurrentLaunchOptions.Instance.Name}:{CurrentLaunchOptions.Instance.Info}", HintType.Red) Return False End If @@ -57,6 +57,10 @@ Public Module ModLaunch FrmLaunchLeft.RefreshButtonsUI() FrmLaunchLeft.RefreshPage(False, False) End If + If McInstanceSelected IsNot Nothing AndAlso McInstanceLock.IsLocked(McInstanceSelected) Then + Hint($"版本 {McInstanceSelected.Name} 正在被修改,请稍后再启动!", HintType.Red) + Return False + End If FrmMain.AprilGiveup() '禁止进入版本选择页面(否则就可以在启动中切换 McInstanceSelected 了) FrmMain.PageStack = FrmMain.PageStack.Where(Function(p) p.Page <> FormMain.PageType.InstanceSelect).ToList @@ -70,7 +74,12 @@ Public Module ModLaunch ''' Public Sub McLaunchLog(Text As String, Optional Level As LogLevel = LogLevel.Info) Text = FilterUserName(FilterAccessToken(Text, "*"), "*") - If ModeDebug Then RunInUi(Sub() FrmLaunchRight.LabLog.Text += $"{vbCrLf}[{Date.Now:HH':'mm':'ss'.'fff}] {Text}") + If ModeDebug Then + RunInUi( + Sub() + FrmLaunchRight.LabLog.AppendText($"[{Date.Now:HH':'mm':'ss'.'fff}] {Text}{vbCrLf}") + End Sub) + End If Logger.Log(Text, Level) End Sub @@ -196,25 +205,6 @@ NextInner: End Try End Sub - ''' - ''' 取消正在进行的游戏启动。 - ''' - Public Sub McLaunchCancel() - If McLaunchLoaderReal IsNot Nothing Then - McLaunchLoaderReal.Cancel() - McLaunchLog("已取消启动") - Try - If McLaunchWatcher IsNot Nothing Then - McLaunchWatcher.Kill() - ElseIf McLaunchProcess IsNot Nothing Then - If Not McLaunchProcess.HasExited Then McLaunchProcess.Kill() - End If - Catch ex As Exception - Logger.Error(ex, "取消启动结束进程失败", LogBehavior.Toast) - End Try - End If - End Sub - #End Region #Region "内存优化" @@ -249,7 +239,7 @@ NextInner: '检查版本 If McInstanceSelected Is Nothing Then Throw New Exception("未选择 Minecraft 版本!") McInstanceSelected.Load() - If McInstanceSelected.State = McInstanceState.Error Then Throw New Exception("Minecraft 存在问题:" & McInstanceSelected.Info) + If McInstanceSelected.State = McInstance.McInstanceState.Error Then Throw New Exception("Minecraft 存在问题:" & McInstanceSelected.Info) '检查输入信息 Dim CheckResult As String = "" RunInUiWait(Sub() CheckResult = McLoginAble(McLoginInput())) @@ -557,7 +547,7 @@ NextInner: '检查是否已经登录完成 Dim ExpiresAt = Settings.Get(Of Long)("CacheMsV2Expires") If Not Data.IsForceRestarting AndAlso '不要求强行重启 - ExpiresAt > 0 AndAlso ExpiresAt > GetUnixTimestampUtc() AndAlso 'AccessToken 尚未过期 + ExpiresAt > 0 AndAlso ExpiresAt > ToUnixTimestampUtc(Date.UtcNow) AndAlso 'AccessToken 尚未过期 Input.UserName = Settings.Get(Of String)("CacheMsV2Name") Then Data.Output = New McLoginResult With { .Name = Input.UserName, .Type = "Microsoft", @@ -1052,7 +1042,7 @@ Retry: Dim ResultJson As JObject = Result.DeserializeJson() Return ( ResultJson("access_token").ToString, - ResultJson("expires_in").ToObject(Of Integer) + GetUnixTimestampUtc() - 1200) '提前 20 分钟视作过期 + ResultJson("expires_in").ToObject(Of Integer) + GetUnixTimestampUtc() - 3600) '提前 1 小时视作过期 End Function '微软登录步骤 5:验证微软账号是否持有 MC,这也会刷新 XGP Private Sub MsLoginStep5(AccessToken As String) @@ -1382,16 +1372,11 @@ NextInstance: 'Log4j 漏洞防御参数 Args.Add("-Dlog4j2.formatMsgNoLookups=true") - '编码(#4700、#5892、#5909、#6934、#8950) - Dim EncodingName As String - Select Case McLaunchJavaSelected.Version.Major - Case Is >= 21 'Java 21+ 会固定使用 UTF-8 - EncodingName = "UTF-8" - Case Is >= 18 'Java 18~20 时,启动 MC 时会添加 COMPAT 参数,因此 Java 会回退到 native.encoding - EncodingName = McLaunchJavaSelected.NativeEncodingName - Case Else 'Java 17- 时,直接使用 file.encoding - EncodingName = McLaunchJavaSelected.FileEncodingName - End Select + '编码(#4700、#5892、#5909、#6934、#8950、#9241) + Dim UseCompatEncoding = McLaunchJavaSelected.Version.Major >= 18 + Dim EncodingName = If(UseCompatEncoding, + McLaunchJavaSelected.NativeEncodingName, 'Java 18+ 会添加 COMPAT,因此 Java 会回退到 native.encoding + McLaunchJavaSelected.FileEncodingName) 'Java 17- 直接使用 file.encoding Try McLaunchEncoding = Encoding.GetEncoding(If(EncodingName.Equals("GBK", StringComparison.OrdinalIgnoreCase), "GB18030", EncodingName)) Catch ex As Exception @@ -1405,7 +1390,7 @@ NextInstance: Dim StderrArgName = If(McLaunchJavaSelected.Version.Major < 19, "sun.stderr.encoding", "stderr.encoding") If Not Args.Any(Function(a) a.StartsWithF($"-D{StdoutArgName}=")) Then Args.Add($"-D{StdoutArgName}={LaunchEncodingName}") If Not Args.Any(Function(a) a.StartsWithF($"-D{StderrArgName}=")) Then Args.Add($"-D{StderrArgName}={LaunchEncodingName}") - If McLaunchJavaSelected.Version.Major >= 18 AndAlso McLaunchJavaSelected.Version.Major < 21 Then Args.Add("-Dfile.encoding=COMPAT") '使用 COMPAT 防止部分 Mod 配置文件不兼容 UTF-8 + If UseCompatEncoding AndAlso Not Args.Any(Function(a) a.StartsWithF("-Dfile.encoding=")) Then Args.Add("-Dfile.encoding=COMPAT") '防止老版本 MC 与部分 Mod 不兼容 UTF-8 '为 JLW 添加 -jar,它必须放在最后 If UseJLW Then @@ -1465,7 +1450,8 @@ NextInstance: Arg &= " " & CustomArg '把 OptiFineForgeTweaker 放在参数的末尾 - If (McInstanceSelected.Version.HasForge OrElse McInstanceSelected.Version.HasLiteLoader) AndAlso McInstanceSelected.Version.HasOptiFine Then + If (McInstanceSelected.Version.ModLoaders.HasFlagF(ModLoaders.Forge) OrElse McInstanceSelected.Version.ModLoaders.HasFlagF(ModLoaders.LiteLoader)) AndAlso + McInstanceSelected.Version.HasOptiFine AndAlso Not McInstanceSelected.Version.OptiFineAsMod Then If Arg.Contains("--tweakClass optifine.OptiFineForgeTweaker") Then McLaunchLog("发现正确的 OptiFineForge TweakClass,目前的游戏参数:" & Arg) Arg = Arg.Replace(" --tweakClass optifine.OptiFineForgeTweaker", "") & " --tweakClass optifine.OptiFineForgeTweaker" @@ -1520,10 +1506,10 @@ NextInstance: Private Iterator Function McLaunchArgumentsReplace(Arguments As List(Of String)) As IEnumerable(Of (Key As String, Value As String)) '基础参数 Yield ("${classpath_separator}", ";") - Yield ("${natives_directory}", GetNativesFolder().TrimEnd("\"c)) + Yield ("${natives_directory}", PathUtils.RemoveSlashSuffix(GetNativesFolder())) Yield ("${library_directory}", PathUtils.ToShortPath(McFolderSelected & "libraries")) Yield ("${libraries_directory}", PathUtils.ToShortPath(McFolderSelected & "libraries")) - Yield ("${pure_directory}", PathPure.Value.TrimEnd("\"c)) '由 PCL 添加,这会允许在分割参数并去重后再替换路径,防止路径中的特殊字符影响参数分割和去重 + Yield ("${pure_directory}", PathUtils.RemoveSlashSuffix(PathPure.Value)) '由 PCL 添加,这会允许在分割参数并去重后再替换路径,防止路径中的特殊字符影响参数分割和去重 Yield ("${launcher_name}", "PCL") Yield ("${launcher_version}", VersionCode) Yield ("${version_name}", McInstanceSelected.Name) @@ -1568,9 +1554,8 @@ NextInstance: Case Else TargetSize = New Size(854, 480) End Select - If McInstanceSelected.Version.Vanilla.Major <= 12 AndAlso - McLaunchJavaSelected.Version >= New Version(8, 0, 200) AndAlso McLaunchJavaSelected.Version <= New Version(8, 0, 321) AndAlso - Not McInstanceSelected.Version.HasOptiFine AndAlso Not McInstanceSelected.Version.HasForge Then '修复 1.12.2- 下 JRE 8u200~321 下窗口大小为设置大小的 DPI% 倍(#3463) + If McInstanceSelected.Version.Vanilla.Major <= 12 AndAlso McLaunchJavaSelected.Version >= New Version(8, 0, 200) AndAlso McLaunchJavaSelected.Version <= New Version(8, 0, 321) AndAlso + Not McInstanceSelected.Version.HasOptiFine AndAlso Not McInstanceSelected.Version.ModLoaders.HasFlagF(ModLoaders.Forge) Then '修复 1.12.2- 下 JRE 8u200~321 下窗口大小为设置大小的 DPI% 倍(#3463) McLaunchLog($"已应用窗口大小过大修复(Java 版本:{McLaunchJavaSelected.Version})") TargetSize.Width /= DPI / 96 TargetSize.Height /= DPI / 96 @@ -1694,7 +1679,7 @@ NextInstance: End Try For Each Entry In Zip.Entries Dim FileName As String = Entry.FullName - If FileName.EndsWithF(".dll", True) Then + If PathUtils.GetExtension(FileName) = "dll" Then '实际解压文件的步骤 Dim FilePath As String = Target & FileName ExistFiles.Add(FilePath) @@ -1738,8 +1723,9 @@ NextInstance: ''' ''' 获取 Natives 文件夹路径,以 \ 结尾。 ''' - Private Function GetNativesFolder() As String - Dim Result As String = PathUtils.AddSlashSuffix(PathUtils.ToShortPath(McInstanceSelected.PathVersion)) & McInstanceSelected.Name & "-natives\" + Public Function GetNativesFolder(Optional Instance As McInstance = Nothing) As String + If Instance Is Nothing Then Instance = McInstanceSelected + Dim Result As String = PathUtils.AddSlashSuffix(PathUtils.ToShortPath(Instance.PathVersion)) & Instance.Name & "-natives\" If Not (IsGBKEncoding OrElse Result.IsAsciiOnly()) Then Result = Paths.AppData & ".minecraft\bin\natives\" If Not Result.IsAsciiOnly() Then @@ -1865,7 +1851,7 @@ NextInstance: Dim CurrentLang As String = ReadIni(SetupFileAddress, "lang", "none") Dim RequiredLang As String = If(CurrentLang = "none" OrElse Not DirectoryUtils.Exists(McInstanceSelected.PathIndie & "saves"), '#3844,整合包可能已经自带了 options.txt If(Settings.Get(Of Boolean)("ToolHelpChinese"), "zh_cn", "en_us"), CurrentLang.Lower) - If Not McInstanceSelected.Version.Vaild OrElse McInstanceSelected.Version.Vanilla.Major <= 10 Then + If Not McInstanceSelected.Version.Valid OrElse McInstanceSelected.Version.Vanilla.Major <= 10 Then '将最后两位改为大写,前面的部分保留 RequiredLang = RequiredLang.Substring(0, RequiredLang.Length - 2) & RequiredLang.Substring(RequiredLang.Length - 2).Upper End If @@ -1908,53 +1894,40 @@ NextInstance: If McLoginLoader.Input.Type = McLoginType.Legacy AndAlso Settings.Get(Of Integer)("LaunchSkinType") = 4 AndAlso FileUtils.Exists(Paths.AppDataThenName & "CustomSkin.png") Then Dim MetaFileAddress As String = PathTemp & "pack.mcmeta" Dim PackPicAddress As String = PathTemp & "pack.png" - Dim PackFormat As Integer - Select Case McInstanceSelected.Version.Vanilla.Major - Case 0, 1, 2, 3, 4, 5 - '更早的版本没有资源包 - McLaunchLog("Minecraft 版本过老,尚不支持自定义离线皮肤") - GoTo IgnoreCustomSkin - Case 6, 7, 8 - PackFormat = 1 - Case 9, 10 - PackFormat = 2 - Case 11, 12 - PackFormat = 3 - Case 13, 14 - PackFormat = 4 - Case 15 - PackFormat = 5 - Case 16 - PackFormat = 6 - Case 17 - PackFormat = 7 - Case 18 - If McInstanceSelected.Version.Vanilla.Minor <= 2 Then - PackFormat = 8 - Else - PackFormat = 9 - End If - Case 19 - If McInstanceSelected.Version.Vanilla.Minor <= 3 Then - PackFormat = 9 - Else - PackFormat = 12 - End If - Case 20 - If McInstanceSelected.Version.Vanilla.Minor <= 1 Then - PackFormat = 15 - Else - PackFormat = 17 - End If - Case Else '快照版是 9999 - PackFormat = 17 - 'https://zh.minecraft.wiki/w/数据包#数据包版本 - End Select - McLaunchLog("正在构建自定义皮肤资源包,格式为:" & PackFormat) + If McInstanceSelected.Version.Vanilla.Major < 6 Then + McLaunchLog("Minecraft 版本过老,尚不支持自定义离线皮肤") + GoTo IgnoreCustomSkin + End If + Dim PackVersionJToken As JToken = McInstanceSelected.JsonVersion?("pack_version") + If TypeOf PackVersionJToken Is JObject Then PackVersionJToken = DirectCast(PackVersionJToken, JObject)("resource") '从 1.17 开始,可以从 pack_version.resource 获取资源包版本 + Dim PackVersion As Integer = Val(PackVersionJToken?.ToString) + If PackVersion <= 0 Then '没有对应字段的老版本 MC,或兜底 + Select Case McInstanceSelected.Version.Vanilla.Major + Case 6, 7, 8 + PackVersion = 1 + Case 9, 10 + PackVersion = 2 + Case 11, 12 + PackVersion = 3 + Case 13, 14 + PackVersion = 4 + Case 15 + PackVersion = 5 + Case 16 + PackVersion = If(McInstanceSelected.Version.Vanilla.Build <= 1, 5, 6) + Case 17 + PackVersion = 7 + Case 18 + PackVersion = 8 + Case Else '旧版快照可能没有可用的资源包格式声明 + PackVersion = 17 + End Select + End If + McLaunchLog($"正在构建自定义皮肤资源包,版本号 {PackVersion},MC 版本 {McInstanceSelected.Version.Vanilla}") '准备文件 Dim Bit As New MyBitmap(PathImage & "Heads/Logo.png") Bit.Save(PackPicAddress) - FileUtils.Write(MetaFileAddress, "{""pack"":{""pack_format"":" & PackFormat & ",""description"":""PCL 自定义离线皮肤资源包""}}") + FileUtils.Write(MetaFileAddress, "{""pack"":{""pack_format"":" & PackVersion & ",""description"":""PCL 自定义离线皮肤资源包""}}") Dim Skin As New MyBitmap(Paths.AppDataThenName & "CustomSkin.png") If (McInstanceSelected.Version.Vanilla.Major = 6 OrElse McInstanceSelected.Version.Vanilla.Major = 7) AndAlso Skin.Pic.Height = 64 Then McLaunchLog("该 Minecraft 版本不支持双层皮肤,已进行裁剪") diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb index f6ddaba..ee84df6 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb @@ -15,7 +15,7 @@ Public Module ModMinecraft End Set End Property Private Sub SetMcFolder(Value As String) '等同 McFolderSelected.Set - If _McFolderSelected = Value Then Return + If PathUtils.IsSame(_McFolderSelected, Value.Replace("$", Paths.Base)) Then Return _McFolderSelected = Value.Replace("$", Paths.Base) _McFolderSelected = PathUtils.AddSlashSuffix(PathUtils.ForCompare(_McFolderSelected)) Settings.Set("LaunchFolderSelect", Value.Replace(Paths.Base, "$")) @@ -44,7 +44,7 @@ Public Module ModMinecraft Public Overrides Function Equals(obj As Object) As Boolean If Not (TypeOf obj Is McFolder) Then Return False Dim Folder = DirectCast(obj, McFolder) - Return Name = Folder.Name AndAlso Location = Folder.Location AndAlso Type = Folder.Type + Return Name = Folder.Name AndAlso PathUtils.IsSame(Location, Folder.Location) AndAlso Type = Folder.Type End Function Public Overrides Function ToString() As String Return Location @@ -76,7 +76,7 @@ Public Module ModMinecraft '扫描官启文件夹 Dim MojangPath As String = Paths.AppData & ".minecraft\" - If (Not CacheMcFolderList.Any OrElse MojangPath <> CacheMcFolderList(0).Location) AndAlso '当前文件夹不是官启文件夹 + If (Not CacheMcFolderList.Any OrElse Not PathUtils.IsSame(MojangPath, CacheMcFolderList(0).Location)) AndAlso '当前文件夹不是官启文件夹 DirectoryUtils.Exists(MojangPath & "versions\") Then '具有权限且存在 versions 文件夹 CacheMcFolderList.Add(New McFolder With {.Name = "官方启动器文件夹", .Location = MojangPath, .Type = McFolder.Types.Vanilla}) End If @@ -98,7 +98,7 @@ Public Module ModMinecraft '若已有该文件夹,则直接重命名;没有则添加 Dim Renamed As Boolean = False For Each OriginalFolder As McFolder In CacheMcFolderList - If OriginalFolder.Location = FolderPath Then + If PathUtils.IsSame(OriginalFolder.Location, FolderPath) Then OriginalFolder.Name = FolderName OriginalFolder.Type = McFolder.Types.RenamedVanilla Renamed = True @@ -217,6 +217,19 @@ Public Module ModMinecraft Public Class McInstance + Public Enum McInstanceState + [Error] + Original + Snapshot + Fool + OptiFine + Old + Forge + NeoForge + LiteLoader + Fabric + End Enum + ''' ''' 版本文件夹路径,以 \ 结尾。 ''' @@ -303,8 +316,7 @@ Public Module ModMinecraft Public ReadOnly Property Modable As Boolean Get If Not IsLoaded Then Load() - Return Version.HasFabric OrElse Version.HasForgelike OrElse Version.HasLiteLoader OrElse - DisplayType = McInstanceCardType.API '#223 + Return Version.ModLoaders.Flags.Any OrElse DisplayType = McInstanceCardType.API '#223 End Get End Property ''' @@ -313,135 +325,15 @@ Public Module ModMinecraft Public Property Version As McVersion Get If _Version IsNot Nothing Then Return _Version - _Version = New McVersion -#Region "获取游戏版本" Try - '获取发布时间并判断是否为老版本 - Try - If JsonObject("releaseTime") Is Nothing Then - ReleaseTime = New Date(1970, 1, 1, 15, 0, 0) '未知版本也可能显示为 1970 年 - Else - ReleaseTime = JsonObject("releaseTime").ToObject(Of Date) - End If - If ReleaseTime.Year > 2000 AndAlso ReleaseTime.Year < 2013 Then - _Version.VanillaName = "Old" - GoTo VersionSearchFinish - End If - Catch - ReleaseTime = New Date(1970, 1, 1, 15, 0, 0) - End Try - '实验性快照 - If If(JsonObject("type"), "") = "pending" Then - _Version.VanillaName = "pending" - GoTo VersionSearchFinish - End If - '从 PCL 下载的版本信息中获取版本号 - If JsonObject("clientVersion") IsNot Nothing Then - _Version.VanillaName = JsonObject("clientVersion") - GoTo VersionSearchFinish - End If - '从 HMCL 下载的版本信息中获取版本号 - If JsonObject("patches") IsNot Nothing Then - For Each Patch As JObject In JsonObject("patches") - If If(Patch("id"), "").ToString = "game" AndAlso Patch("version") IsNot Nothing Then - _Version.VanillaName = Patch("version").ToString - GoTo VersionSearchFinish - End If - Next - End If - '从 Forge / NeoForge Arguments 中获取版本号 - If JsonObject("arguments") IsNot Nothing AndAlso JsonObject("arguments")("game") IsNot Nothing Then - Dim Mark As Boolean = False - For Each Argument In JsonObject("arguments")("game") - If Mark Then - _Version.VanillaName = Argument.ToString - GoTo VersionSearchFinish - End If - If Argument.ToString = "--fml.mcVersion" Then Mark = True - Next - End If - '从继承版本中获取版本号 - If Not InheritName = "" Then - _Version.VanillaName = If(JsonObject("jar"), "").ToString 'LiteLoader 优先使用 Jar - If _Version.VanillaName = "" Then _Version.VanillaName = InheritName - GoTo VersionSearchFinish - End If - '从下载地址中获取版本号 - Dim Regex As String = If(JsonObject("downloads"), "").ToString.RegexSeek("(?<=launcher.mojang.com/mc/game/)[^/]*") - If Regex IsNot Nothing Then - _Version.VanillaName = Regex - GoTo VersionSearchFinish - End If - '从 Forge 版本中获取版本号 - Dim LibrariesString As String = JsonObject("libraries").ToString - Regex = If(LibrariesString.RegexSeek("(?<=net.minecraftforge:forge:)[0-9]{1,2}.[0-9+.]+"), LibrariesString.RegexSeek("(?<=net.minecraftforge:fmlloader:)[0-9]{1,2}.[0-9+.]+")) - If Regex IsNot Nothing Then - _Version.VanillaName = Regex - GoTo VersionSearchFinish - End If - '从 OptiFine 版本中获取版本号 - Regex = LibrariesString.RegexSeek("(?<=optifine:OptiFine:)[0-9]{1,2}.[0-9+.]+") - If Regex IsNot Nothing Then - _Version.VanillaName = Regex - GoTo VersionSearchFinish - End If - '从 Fabric 版本中获取版本号 - Regex = LibrariesString.RegexSeek("(?<=((fabricmc)|(quiltmc)):intermediary:)[^""]*") - If Regex IsNot Nothing Then - _Version.VanillaName = Regex - GoTo VersionSearchFinish - End If - '从 jar 项中获取版本号 - If JsonObject("jar") IsNot Nothing Then - _Version.VanillaName = JsonObject("jar").ToString - GoTo VersionSearchFinish - End If - '从 jar 文件的 version.json 中获取版本号 - If JsonVersion?("name") IsNot Nothing Then - Dim JsonVerName As String = JsonVersion("name").ToString - If JsonVerName.Length < 32 Then '因为 wiki 说这玩意儿可能是个 hash,虽然我没发现 - _Version.VanillaName = JsonVerName - Logger.Info($"从版本 jar 中的 version.json 获取到版本号:{JsonVerName}") - GoTo VersionSearchFinish - End If - End If - '从 JSON 的 ID 中获取 - Static Pattern = "(([1-9][0-9]w[0-9]{2}[a-g])|((1|[2-9][0-9])\.[0-9]+(\.[0-9]+)?(-(pre|rc|snapshot-?)[1-9]*| Pre-Release( [1-9])?)?))(_unobfuscated)?" - Regex = JsonObject("id").ToString.RegexSeek(Pattern, RegularExpressions.RegexOptions.IgnoreCase) - If Regex IsNot Nothing Then - _Version.VanillaName = Regex - GoTo VersionSearchFinish - End If - '非准确的版本判断警告 - Logger.Info($"无法完全确认 MC 版本号的版本:{Name}") - _Version.Reliable = False - '从文件夹名中获取 - Regex = Name.RegexSeek(Pattern, RegularExpressions.RegexOptions.IgnoreCase) - If Regex IsNot Nothing Then - _Version.VanillaName = Regex - GoTo VersionSearchFinish - End If - '从 JSON 出现的版本号中获取 - Dim JsonRaw As JObject = JsonObject.DeepClone() - JsonRaw.Remove("libraries") - Dim JsonRawText As String = JsonRaw.ToString - Regex = JsonRawText.RegexSeek(Pattern, RegularExpressions.RegexOptions.IgnoreCase) - If Regex IsNot Nothing Then - _Version.VanillaName = Regex - GoTo VersionSearchFinish - End If - '无法获取 - _Version.VanillaName = "Unknown" - Info = "PCL 无法识别该版本的 MC 版本号" + Dim Result = New McVersion(Name, JsonObject, JsonVersion) + _Version = Result + If Result.VanillaName = "Unknown" Then Info = "PCL 无法识别该版本的 MC 版本号" Catch ex As Exception Logger.Warn(ex, "识别 Minecraft 版本时出错") - _Version.VanillaName = "Unknown" + _Version = New McVersion("Unknown") Info = "无法识别:" & ex.Message End Try -#End Region -VersionSearchFinish: - '获取版本号 - _Version.VanillaName = _Version.VanillaName.Replace("_unobfuscated", "").Replace(" Unobfuscated", "") Return _Version End Get Set(value As McVersion) @@ -453,7 +345,11 @@ VersionSearchFinish: ''' ''' 版本的发布时间。 ''' - Public ReleaseTime As New Date(1970, 1, 1, 15, 0, 0) + Public ReadOnly Property ReleaseTime As Date + Get + Return Version.ReleaseTime + End Get + End Property ''' ''' 获取该版本的 JSON 路径。 @@ -741,42 +637,28 @@ Recheck: State = McInstanceState.Error Case "Old" State = McInstanceState.Old - Case Else '根据 API 进行筛选 - Dim RealJson As String = If(JsonObject, JsonText).ToString + Case Else '愚人节与快照版本 - Dim FixedReleaseTime = ReleaseTime.ToUniversalTime().AddHours(2) - Dim JsonType = If(JsonObject("type"), "").ToString - If JsonType = "fool" OrElse - (FixedReleaseTime.Month = 4 AndAlso FixedReleaseTime.Day = 1 AndAlso JsonType = "snapshot") OrElse - GetMcFoolName(Version.VanillaName) <> "" Then + If Version.IsFool Then State = McInstanceState.Fool - ElseIf IsSnapshot() Then + ElseIf Version.IsSnapshot Then State = McInstanceState.Snapshot End If 'OptiFine - If RealJson.Contains("optifine") Then + If Version.HasOptiFine Then State = McInstanceState.OptiFine - Version.OptiFine = If(RealJson.RegexSeek("(?<=HD_U_)[^"":/]+"), "未知版本") End If 'LiteLoader - If RealJson.Contains("liteloader") Then + If Version.ModLoaders.HasFlagF(ModLoaders.LiteLoader) Then State = McInstanceState.LiteLoader - Version.HasLiteLoader = True End If - 'Fabric、Forge - If RealJson.Contains("net.fabricmc:fabric-loader") OrElse RealJson.Contains("org.quiltmc:quilt-loader") Then + 'Fabric、Forge、NeoForge + If Version.ModLoaders.HasFlagF(ModLoaders.Fabric) Then State = McInstanceState.Fabric - Version.Fabric = If(RealJson.RegexSeek("(?<=(net.fabricmc:fabric-loader:)|(org.quiltmc:quilt-loader:))[0-9\.]+(\+build.[0-9]+)?"), "未知版本").Replace("+build", "") - ElseIf RealJson.Contains("minecraftforge") AndAlso Not RealJson.Contains("net.neoforge") Then + ElseIf Version.ModLoaders.HasFlagF(ModLoaders.Forge) Then State = McInstanceState.Forge - Version.Forge = RealJson.RegexSeek("(?<=forge:[0-9\.]+(_pre[0-9]*)?\-)[0-9\.]+") - If Version.Forge Is Nothing Then Version.Forge = RealJson.RegexSeek("(?<=net\.minecraftforge:minecraftforge:)[0-9\.]+") - If Version.Forge Is Nothing Then Version.Forge = If(RealJson.RegexSeek("(?<=net\.minecraftforge:fmlloader:[0-9\.]+-)[0-9\.]+"), "未知版本") - ElseIf RealJson.Contains("net.neoforge") Then - '1.20.1 JSON 范例:"--fml.forgeVersion", "47.1.99" - '1.20.2+ JSON 范例:"--fml.neoForgeVersion", "20.6.119-beta" + ElseIf Version.ModLoaders.HasFlagF(ModLoaders.NeoForge) Then State = McInstanceState.NeoForge - Version.NeoForge = If(RealJson.RegexSeek("(?<=orgeVersion"",[^""]*?"")[^""]+(?="",)"), "未知版本") End If End Select #End Region @@ -808,18 +690,7 @@ ExitDataLoad: End Select End If '确定版本描述 - Dim CustomInfo As String = ReadIni(PathVersion & "PCL\Setup.ini", "CustomInfo") - If CustomInfo <> "" Then - Info = CustomInfo - Else - Info = VersionDisplayName() - If State = McInstanceState.Fool Then - Info = GetMcFoolName(Version.VanillaName) - ElseIf State <> McInstanceState.Error Then - If Settings.Get(Of Integer)("VersionServerLogin", Instance:=Me) = 3 Then Info += ", 统一通行证验证" - If Settings.Get(Of Integer)("VersionServerLogin", Instance:=Me) = 4 Then Info += ", Authlib 验证" - End If - End If + RefreshInfo() '确定版本收藏状态 IsStar = ReadIni(PathVersion & "PCL\Setup.ini", "IsStar", False) '确定版本显示种类 @@ -832,11 +703,11 @@ ExitDataLoad: End If If State <> McInstanceState.Error AndAlso Version.Reliable Then WriteIni(PathVersion & "PCL\Setup.ini", "ReleaseTime", ReleaseTime.ToString("yyyy'-'MM'-'dd HH':'mm")) - WriteIni(PathVersion & "PCL\Setup.ini", "VersionFabric", Version.Fabric) - WriteIni(PathVersion & "PCL\Setup.ini", "VersionOptiFine", Version.OptiFine) - WriteIni(PathVersion & "PCL\Setup.ini", "VersionLiteLoader", Version.HasLiteLoader) - WriteIni(PathVersion & "PCL\Setup.ini", "VersionForge", Version.Forge) - WriteIni(PathVersion & "PCL\Setup.ini", "VersionNeoForge", Version.NeoForge) + WriteIni(PathVersion & "PCL\Setup.ini", "VersionFabric", If(Version.ModLoaders.HasFlagF(ModLoaders.Fabric), Version.ModLoaderVersions(ModLoaders.Fabric), Nothing)) + WriteIni(PathVersion & "PCL\Setup.ini", "VersionOptiFine", If(Version.OptiFineAsMod, Nothing, Version.OptiFine)) + WriteIni(PathVersion & "PCL\Setup.ini", "VersionLiteLoader", Version.ModLoaders.HasFlagF(ModLoaders.LiteLoader)) + WriteIni(PathVersion & "PCL\Setup.ini", "VersionForge", If(Version.ModLoaders.HasFlagF(ModLoaders.Forge), Version.ModLoaderVersions(ModLoaders.Forge), Nothing)) + WriteIni(PathVersion & "PCL\Setup.ini", "VersionNeoForge", If(Version.ModLoaders.HasFlagF(ModLoaders.NeoForge), Version.ModLoaderVersions(ModLoaders.NeoForge), Nothing)) WriteIni(PathVersion & "PCL\Setup.ini", "VersionVanillaName", Version.VanillaName) WriteIni(PathVersion & "PCL\Setup.ini", "VersionVanilla", Version.Vanilla.ToString) End If @@ -850,26 +721,36 @@ ExitDataLoad: End Try Return Me End Function - Private Function IsSnapshot() As Boolean - Dim jsonType As String = If(JsonObject("type"), "").ToString - Return {"w", "snapshot", "rc", "pre", "experimental", "-"}.Any(Function(s) Version.VanillaName.ContainsIgnoreCase(s)) OrElse - Name.ContainsIgnoreCase("combat") OrElse - jsonType = "snapshot" OrElse jsonType = "pending" OrElse jsonType = "fool" - End Function - Public IsLoaded As Boolean = False ''' + ''' 根据当前组件信息刷新版本描述。 + ''' + Public Sub RefreshInfo() + Dim CustomInfo As String = ReadIni(PathVersion & "PCL\Setup.ini", "CustomInfo") + If CustomInfo <> "" Then + Info = CustomInfo + Else + Info = VersionDisplayName() + If State = McInstanceState.Fool Then + Info = GetMcFoolName(Version.VanillaName) + ElseIf State <> McInstanceState.Error Then + If Settings.Get(Of Integer)("VersionServerLogin", Instance:=Me) = 3 Then Info += ", 统一通行证验证" + If Settings.Get(Of Integer)("VersionServerLogin", Instance:=Me) = 4 Then Info += ", Authlib 验证" + End If + End If + End Sub + ''' ''' 获取对版本信息的简短描述。 ''' 例如 “快照 16w01a”、“原版 1.12.2, Forge 1.2.3”、“愚人节版本 2.0”。 ''' Public Function VersionDisplayName() As String 'Mod Loader 信息 Dim ModLoaderInfo As String = "" - If Version.HasForge Then ModLoaderInfo += ", Forge" & If(Version.Forge = "未知版本", "", " " & Version.Forge) - If Version.HasNeoForge Then ModLoaderInfo += ", NeoForge" & If(Version.NeoForge = "未知版本", "", " " & Version.NeoForge) - If Version.HasFabric Then ModLoaderInfo += ", Fabric" & If(Version.Fabric = "未知版本", "", " " & Version.Fabric) + If Version.ModLoaders.HasFlagF(ModLoaders.Forge) Then ModLoaderInfo += ", Forge" & If(Version.ModLoaderVersions(ModLoaders.Forge) = "未知版本", "", " " & Version.ModLoaderVersions(ModLoaders.Forge)) + If Version.ModLoaders.HasFlagF(ModLoaders.NeoForge) Then ModLoaderInfo += ", NeoForge" & If(Version.ModLoaderVersions(ModLoaders.NeoForge) = "未知版本", "", " " & Version.ModLoaderVersions(ModLoaders.NeoForge)) + If Version.ModLoaders.HasFlagF(ModLoaders.Fabric) Then ModLoaderInfo += ", Fabric" & If(Version.ModLoaderVersions(ModLoaders.Fabric) = "未知版本", "", " " & Version.ModLoaderVersions(ModLoaders.Fabric)) If Version.HasOptiFine Then ModLoaderInfo += ", OptiFine" & If(Version.OptiFine = "未知版本", "", " " & Version.OptiFine.Replace("-", " ").Replace("_", " ")) - If Version.HasLiteLoader Then ModLoaderInfo += ", LiteLoader" + If Version.ModLoaders.HasFlagF(ModLoaders.LiteLoader) Then ModLoaderInfo += ", LiteLoader" '基础信息 Dim Info As String Select Case State @@ -882,7 +763,7 @@ ExitDataLoad: Info = "实验性快照" & Version.VanillaName ElseIf Version.VanillaName = "pending" Then Info = "实验性快照" - ElseIf IsSnapshot() Then + ElseIf Version.IsSnapshot Then Info = If(Version.Reliable, "快照版 " & Version.VanillaName.Replace("-snapshot", ""), "快照版") Else Info = If(Version.Reliable, "正式版 " & Version.VanillaName, "正式版") @@ -927,287 +808,91 @@ ExitDataLoad: End Function) With {.Height = 42} End Function + ''' + ''' 重命名 MC 版本。 + ''' + Public Function Rename(NewName As String) As McInstance + Dim OldName = Name + Dim OldPath = PathVersion + Dim NewPath = PathUtils.AddSlashSuffix(Path.Combine(PathUtils.RemoveLastPart(OldPath), NewName)) + '重新读取版本 JSON 信息,避免 JsonObject 中已被合并的项被重新存储 + Dim JsonObject As JObject + Dim OldJsonPath = GetJsonPath() + Try + JsonObject = FileUtils.ReadAsJson(OldJsonPath) + Catch ex As Exception + Logger.Warn(ex, "在重命名读取 JSON 时失败") + JsonObject = Me.JsonObject + End Try + '清理缓存 + Config.Save() + ResetSettingsCache() + '重命名主文件夹 + DirectoryUtils.Move(OldPath, NewPath) + '重命名 jar 文件与 natives 文件夹 + '不能进行遍历重命名,否则在版本名很短的时候容易误伤其他文件(#6443) + If DirectoryUtils.Exists($"{NewPath}{OldName}-natives") Then DirectoryUtils.Move($"{NewPath}{OldName}-natives", $"{NewPath}{NewName}-natives") + If FileUtils.Exists($"{NewPath}{OldName}.jar") Then FileUtils.Move($"{NewPath}{OldName}.jar", $"{NewPath}{NewName}.jar") + '替换版本设置文件中的路径 + If FileUtils.Exists(NewPath & "PCL\Setup.ini") Then + FileUtils.Write(NewPath & "PCL\Setup.ini", FileUtils.ReadAsString(NewPath & "PCL\Setup.ini").Replace(OldPath, NewPath)) + End If + '更改已选中的版本 + If ReadIni(McFolderSelected & "PCL.ini", "Version") = OldName Then WriteIni(McFolderSelected & "PCL.ini", "Version", NewName) + '更新版本 Json + Try + JsonObject("id") = NewName + FileUtils.Delete(NewPath & PathUtils.GetLastPart(OldJsonPath)) + FileUtils.Write(NewPath & NewName & ".json", JsonObject.ToString) + Catch ex As Exception + Logger.Warn(ex, "重命名版本 json 失败") + End Try + Return New McInstance(NewPath).Load() + End Function + '运算符支持 Public Overrides Function Equals(obj As Object) As Boolean Dim version = TryCast(obj, McInstance) - Return version IsNot Nothing AndAlso PathVersion = version.PathVersion + Return version IsNot Nothing AndAlso PathUtils.IsSame(PathVersion, version.PathVersion) End Function Public Shared Operator =(a As McInstance, b As McInstance) As Boolean If a Is Nothing AndAlso b Is Nothing Then Return True If a Is Nothing OrElse b Is Nothing Then Return False - Return a.PathVersion = b.PathVersion + Return PathUtils.IsSame(a.PathVersion, b.PathVersion) End Operator Public Shared Operator <>(a As McInstance, b As McInstance) As Boolean Return Not (a = b) End Operator + End Class - Public Enum McInstanceState - [Error] - Original - Snapshot - Fool - OptiFine - Old - Forge - NeoForge - LiteLoader - Fabric - End Enum ''' - ''' Minecraft 版本号和附加组件信息。 + ''' MC 版本独占锁。 ''' - Public Class McVersion - - '原版 - - ''' - ''' 原版版本名。 - ''' 如 26.1,26.1-snapshot-1,1.12.2,16w01a。 - ''' - Public Property VanillaName As String + Public NotInheritable Class McInstanceLock + Private Shared ReadOnly LockedPaths As New ConcurrentDictionary(Of String, IWorker)(StringComparer.OrdinalIgnoreCase) + Friend Shared ReadOnly Property IsAnyLocked As Boolean Get - Return _VanillaName - End Get - Set(value As String) - If _VanillaName = value Then Return - _VanillaName = value - Vanilla = NameToVersion(_VanillaName) - End Set - End Property - Private _VanillaName As String - ''' - ''' 可比较的三段式原版版本号。 - ''' 对老版本格式,例如 1.20.1,会被转换为 20.0.1。 - ''' 若没有版本号,例如旧快照,则为 9999.0.0。 - ''' - Public Vanilla As Version - ''' - ''' 指示原版版本号是否可靠(不是通过猜测获取)。 - ''' - Public Reliable As Boolean = True - ''' - ''' 原版版本号是否有效。 - ''' - Public ReadOnly Property Vaild As Boolean - Get - Return Vanilla.Major < 1000 + Return Not LockedPaths.IsEmpty End Get End Property ''' - ''' 可供比较的原版 Drop 序数。 - ''' 例如 26.3.2 为 263,1.21.5 为 210。 - ''' 若没有版本号,例如旧快照,则直接指定为 209。 + ''' 在 结束前,锁定指定的 MC 版本。当 结束时,会自动将锁定释放。 + ''' 仅在该版本已被锁定时返回 false,此时不会改变锁定状态。 ''' - Public ReadOnly Property Drop As Integer - Get - Return If(Vaild, Vanilla.Major * 10 + Vanilla.Minor, 209) - End Get - End Property - - 'OptiFine - - ''' - ''' 该版本是否通过 JSON 安装了 OptiFine。 - ''' - Public ReadOnly Property HasOptiFine As Boolean - Get - Return OptiFine IsNot Nothing - End Get - End Property - ''' - ''' OptiFine 版本名,如 C8、C9_pre10。 - ''' - Public OptiFine As String = Nothing - ''' - ''' 可供比较的 OptiFine 版本序数。 - ''' - Public ReadOnly Property OptiFineCode As Integer - Get - If OptiFine Is Nothing OrElse OptiFine = "未知版本" Then Return 0 - '字母编号,如 G2 中的 G(7) - Dim Result As Integer = Asc(OptiFine.Upper.First) - Asc("A"c) + 1 - '末尾数字,如 C5 beta4 中的 5 - Result *= 100 - Result += Val(Right(OptiFine, OptiFine.Length - 1).RegexSeek("[0-9]+")) - '测试标记(正式版为 99,Pre[x] 为 50+x,Beta[x] 为 x) - Result *= 100 - If OptiFine.ContainsIgnoreCase("pre") Then Result += 50 - If OptiFine.ContainsIgnoreCase("pre") OrElse OptiFine.ContainsIgnoreCase("beta") Then - If Val(Right(OptiFine, 1)) = 0 AndAlso Right(OptiFine, 1) <> "0" Then - Result += 1 '为 pre 或 beta 结尾,视作 1 - Else - Result += Val(OptiFine.Lower.RegexSeek("(?<=((pre)|(beta)))[0-9]+")) - End If - Else - Result += 99 - End If - Return Result - End Get - End Property - - 'Forge/NeoForge - - ''' - ''' 该版本是否安装了 Forge。 - ''' - Public ReadOnly Property HasForge As Boolean - Get - Return Forge IsNot Nothing - End Get - End Property - ''' - ''' 该版本是否安装了 NeoForge。 - ''' - Public ReadOnly Property HasNeoForge As Boolean - Get - Return NeoForge IsNot Nothing - End Get - End Property - ''' - ''' 该版本是否安装了 Forge 或 NeoForge。 - ''' - Public ReadOnly Property HasForgelike As Boolean - Get - Return HasForge OrElse HasNeoForge - End Get - End Property - ''' - ''' Forge 版本名,如 31.1.2、14.23.5.2847。 - ''' - Public Forge As String = Nothing - ''' - ''' NeoForge 版本名,如 47.1.79、21.0.2-beta、26.1.0.0-alpha.14+snapshot-11。 - ''' - Public NeoForge As String = Nothing - ''' - ''' 可供比较的类 Forge 版本序数。 - ''' - Public ReadOnly Property ForgelikeCode As Integer - Get - If Not HasForgelike Then Return 0 - If (Forge Is Nothing OrElse Forge = "未知版本") AndAlso - (NeoForge Is Nothing OrElse NeoForge = "未知版本") Then Return 0 - Dim Segments = If(HasForge, Forge, NeoForge).RegexSearch("\d+").ToList - Select Case Segments.Count - Case Is > 4 - Return Val(Segments(0)) * 1000000 + Val(Segments(1)) * 10000 + Val(Segments(3)) - Case 3 - Return Val(Segments(0)) * 1000000 + Val(Segments(1)) * 10000 + Val(Segments(2)) - Case 2 - Return Val(Segments(0)) * 1000000 + Val(Segments(1)) * 10000 - Case Else - Return Val(Segments(0)) * 1000000 - End Select - End Get - End Property - - 'Fabric - - ''' - ''' 该版本是否安装了 Fabric。 - ''' - Public ReadOnly Property HasFabric As Boolean - Get - Return Fabric IsNot Nothing - End Get - End Property - ''' - ''' Fabric 版本名,如 0.7.2.175。 - ''' - Public Fabric As String = Nothing - - 'LiteLoader - - ''' - ''' 该版本是否安装了 LiteLoader。 - ''' - Public HasLiteLoader As Boolean = False - - 'Helpers - - ''' - ''' 获取该版本包含的 Mod Loader。 - ''' - Public ReadOnly Property ModLoaders As ModLoaders - Get - Dim Result As ModLoaders = ModLoaders.None - If HasForge Then Result = Result Or ModLoaders.Forge - If HasNeoForge Then Result = Result Or ModLoaders.NeoForge - If HasFabric Then Result = Result Or ModLoaders.Fabric - If HasLiteLoader Then Result = Result Or ModLoaders.LiteLoader - Return Result - End Get - End Property - ''' - ''' 版本字符串是否符合 Minecraft 原版格式,例如 1.x、26.x。 - ''' - Public Shared Function IsFormatFit(Version As String) As Boolean - If Version Is Nothing Then Return False - If Version.RegexCheck("^1\.\d") Then Return True - If Val(Version.RegexSeek("^[2-9]\d\.\d+")) >= 26 Then Return True - Return False + Public Shared Function TryAcquire(Worker As IWorker, InstancePath As String) As Boolean + InstancePath = PathUtils.ForCompare(InstancePath) + If Not LockedPaths.TryAdd(InstancePath, Worker) Then Return False + AddHandler Worker.Stopped, Sub() LockedPaths.TryRemove(InstancePath, Nothing) + Return True End Function ''' - ''' 将版本名转换为 Drop 序数。 - ''' 若无法转换则返回 209。 + ''' 检查 MC 版本是否正被锁定。 ''' - Public Shared Function VersionToDrop(VanillaName As String) As Integer - If VanillaName Is Nothing Then Return 209 - Dim Version = NameToVersion(VanillaName) - If Version.Major >= 1000 Then Return 209 - Return Version.Major * 10 + Version.Minor + Public Shared Function IsLocked(Instance As McInstance) As Boolean + If Instance Is Nothing Then Return False + Return LockedPaths.ContainsKey(PathUtils.ForCompare(Instance.PathVersion)) End Function - ''' - ''' 将 Drop 序数转换为版本字符串。 - ''' - Public Shared Function DropToVersion(Drop As Integer) As String - If Drop >= 250 Then - Return $"{Drop \ 10}.{Drop Mod 10}" - ElseIf Drop = 209 Then - Logger.Warn(New Exception("尝试将旧快照版 Drop 序数转换为版本字符串,这不应该发生!")) - Return "1.20" - Else - Return $"1.{Drop \ 10}" - End If - End Function - ''' - ''' 将版本名转换为可比较的三段式原版版本号。 - ''' 对老版本格式,例如 1.20.3,会被转换为 20.0.3。 - ''' 对愚人节版本,返回其基于的正式版版本号。 - ''' 若没有版本号,例如旧快照,则为 9999.0.0。 - ''' - Public Shared Function NameToVersion(Name As String) As Version - Name = Name.Lower.Replace("_unobfuscated", "").Replace(" Unobfuscated", "") - '愚人节版本 - If Name.StartsWithF("2.0") Then Return NameToVersion("1.5.1") - If Name = "15w14a" Then Return NameToVersion("1.8.3") - If Name.Contains(".rv-pre") Then Return NameToVersion("1.9.2") - If Name.Contains("shareware") Then Return NameToVersion("1.13.2") - If Name.StartsWithF("20w14") AndAlso Name <> "20w14a" Then Return NameToVersion("1.15.2") - If Name.Contains("oneblockatatime") Then Return NameToVersion("1.18.2") - If Name.Contains("23w13a") AndAlso Name <> "20w13a" Then Return NameToVersion("1.19.4") - If Name = "24w14potato" Then Return NameToVersion("1.20.4") - If Name = "25w14craftmine" Then Return NameToVersion("1.21.4") - If Name = "26w14a" Then Return NameToVersion("26.1.1") - '其余版本 - If Name.StartsWithF("1.") Then - Dim Segments = Name.Split(" _-.".ToCharArray) - Return New Version( - Val(If(Segments.Count >= 2, Segments(1), "0")), - 0, - Val(If(Segments.Count >= 3, Segments(2), "0"))) - ElseIf Name.RegexCheck("^[2-9][0-9]\.") Then - Dim Segments = Name.Split(" _-.".ToCharArray) - Return New Version( - Val(Segments(0)), - Val(If(Segments.Count >= 2, Segments(1), "0")), - Val(If(Segments.Count >= 3, Segments(2), "0"))) - Else - Return New Version(9999, 0, 0) - End If - End Function - End Class ''' @@ -1315,7 +1000,7 @@ OnLoaded: If SavedSelection <> "" Then For Each Card As KeyValuePair(Of McInstanceCardType, List(Of McInstance)) In McInstanceList For Each Instance As McInstance In Card.Value - If Instance.Name = SavedSelection AndAlso Not Instance.State = McInstanceState.Error Then + If Instance.Name = SavedSelection AndAlso Not Instance.State = McInstance.McInstanceState.Error Then '使用已储存的选择 McInstanceSelected = Instance Logger.Info($"选择该文件夹储存的 Minecraft 版本:{McInstanceSelected.PathVersion}") @@ -1324,7 +1009,7 @@ OnLoaded: Next Next End If - If Not McInstanceList.First.Value(0).State = McInstanceState.Error Then + If Not McInstanceList.First.Value(0).State = McInstance.McInstanceState.Error Then '自动选择第一项 McInstanceSelected = McInstanceList.First.Value(0) Logger.Info($"自动选择 Minecraft 版本:{McInstanceSelected.PathVersion}") @@ -1372,40 +1057,58 @@ OnLoaded: Instance.Info = ReadIni(Instance.PathVersion & "PCL\Setup.ini", "CustomInfo", "") If Instance.Info = "" Then Instance.Info = ReadIni(Instance.PathVersion & "PCL\Setup.ini", "Info", Instance.Info) Instance.Logo = ReadIni(Instance.PathVersion & "PCL\Setup.ini", "Logo", Instance.Logo) - Instance.ReleaseTime = ReadIni(Instance.PathVersion & "PCL\Setup.ini", "ReleaseTime", Instance.ReleaseTime) Instance.State = ReadIni(Instance.PathVersion & "PCL\Setup.ini", "State", Instance.State) Instance.IsStar = ReadIni(Instance.PathVersion & "PCL\Setup.ini", "IsStar", False) Instance.DisplayType = ReadIni(Folder & "PCL\Setup.ini", "DisplayType", McInstanceCardType.Auto) - If Instance.State <> McInstanceState.Error AndAlso - ReadIni(Instance.PathVersion & "PCL\Setup.ini", "VersionVanillaName", "Unknown") <> "Unknown" Then - '读取版本信息 - Dim ReadCachedInfo = - Function(Key As String) As String - Dim Value = ReadIni(Instance.PathVersion & "PCL\Setup.ini", Key, Nothing) - Return If(Value = "", Nothing, Value) '对老版本 Setup.ini 的空值兼容 - End Function - Dim VersionInfo As New McVersion With { - .Fabric = ReadCachedInfo("VersionFabric"), - .Forge = ReadCachedInfo("VersionForge"), - .NeoForge = ReadCachedInfo("VersionNeoForge"), - .OptiFine = ReadCachedInfo("VersionOptiFine"), - .HasLiteLoader = ReadIni(Instance.PathVersion & "PCL\Setup.ini", "VersionLiteLoader", False), - .VanillaName = ReadIni(Instance.PathVersion & "PCL\Setup.ini", "VersionVanillaName", "Unknown") - } - Instance.Version = VersionInfo + Dim Manifest As McInstallManifest = If(Instance.State = McInstance.McInstanceState.Error, Nothing, Configs.McInstallManifest.Get(Instance.Config)) + '检查 manifest 是否有过期文件,并进行更新 + If Manifest IsNot Nothing AndAlso Manifest.SchemaVersion <= Versions.McInstallManifestVersion AndAlso Manifest.ManagedFiles.Any(Function(File) File.Component IsNot Nothing) Then + Dim LatestManifest = Manifest.RefreshModComponents(Instance.PathIndie) + If LatestManifest IsNot Manifest Then + Manifest = LatestManifest + Configs.McInstallManifest.Set(Manifest, Instance.Config) + Instance.Config.Save() + End If + End If + Dim CachedVanillaName = ReadIni(Instance.PathVersion & "PCL\Setup.ini", "VersionVanillaName", "Unknown") + If Instance.State <> McInstance.McInstanceState.Error AndAlso (Manifest IsNot Nothing OrElse CachedVanillaName <> "Unknown") Then + Dim Version As McVersion + If Manifest IsNot Nothing Then + '从 manifest 中读取版本信息 + Version = New McVersion(Manifest.Components) With { + .OptiFineAsMod = Manifest.ManagedFiles.Any(Function(File) File.Component = "OptiFine")} + Else + '从 Setup.ini 中读取版本信息 + Dim ReadCachedInfo = + Function(Key As String) As String + Dim Value = ReadIni(Instance.PathVersion & "PCL\Setup.ini", Key, Nothing) + Return If(Value = "", Nothing, Value) '对老版本 Setup.ini 的空值兼容 + End Function + Version = New McVersion(CachedVanillaName) With {.OptiFine = ReadCachedInfo("VersionOptiFine")} + For Each Loader In {ModLoaders.Fabric, ModLoaders.Forge, ModLoaders.NeoForge} + Dim CachedVersion = ReadCachedInfo($"Version{Loader}") + If CachedVersion IsNot Nothing Then Version.ModLoaderVersions(Loader) = CachedVersion + Next + If ReadIni(Instance.PathVersion & "PCL\Setup.ini", "VersionLiteLoader", False) Then Version.ModLoaderVersions(ModLoaders.LiteLoader) = "未知版本" + End If + Version.ReleaseTime = ReadIni(Instance.PathVersion & "PCL\Setup.ini", "ReleaseTime", New Date(1970, 1, 1, 15, 0, 0)) + Version.IsFool = Version.IsFool OrElse Instance.State = McInstance.McInstanceState.Fool + Version.IsSnapshot = Version.IsSnapshot OrElse Instance.State = McInstance.McInstanceState.Snapshot OrElse Instance.Name.ContainsIgnoreCase("combat") + Instance.Version = Version + If Manifest IsNot Nothing Then Instance.RefreshInfo() Else Instance.Load() End If '重新检查错误版本 - If Instance.State = McInstanceState.Error Then + If Instance.State = McInstance.McInstanceState.Error Then '重新尝试获取版本信息 Dim OldDesc As String = Instance.Info - Instance.State = McInstanceState.Original + Instance.State = McInstance.McInstanceState.Original Instance.Check() '校验错误原因是否改变 Dim CustomInfo As String = ReadIni(Instance.PathVersion & "PCL\Setup.ini", "CustomInfo") - If Instance.State = McInstanceState.Original OrElse (CustomInfo = "" AndAlso Not OldDesc = Instance.Info) Then + If Instance.State = McInstance.McInstanceState.Original OrElse (CustomInfo = "" AndAlso Not OldDesc = Instance.Info) Then Logger.Info($"版本 {Instance.Name} 的错误状态已变更,新的状态为:{Instance.Info}") Return Nothing End If @@ -1480,41 +1183,41 @@ OnLoaded: If InstanceStar.Any Then InstanceListOriginal.Add(McInstanceCardType.Star, InstanceStar) '预先筛选出愚人节和错误的版本 - McInstanceFilter(InstanceList, InstanceListOriginal, {McInstanceState.Error}, McInstanceCardType.Error) - McInstanceFilter(InstanceList, InstanceListOriginal, {McInstanceState.Fool}, McInstanceCardType.Fool) + McInstanceFilter(InstanceList, InstanceListOriginal, {McInstance.McInstanceState.Error}, McInstanceCardType.Error) + McInstanceFilter(InstanceList, InstanceListOriginal, {McInstance.McInstanceState.Fool}, McInstanceCardType.Fool) '筛选 API 版本 - McInstanceFilter(InstanceList, InstanceListOriginal, {McInstanceState.Forge, McInstanceState.NeoForge, McInstanceState.LiteLoader, McInstanceState.Fabric}, McInstanceCardType.API) + McInstanceFilter(InstanceList, InstanceListOriginal, {McInstance.McInstanceState.Forge, McInstance.McInstanceState.NeoForge, McInstance.McInstanceState.LiteLoader, McInstance.McInstanceState.Fabric}, McInstanceCardType.API) '将老版本预先分类入不常用,只剩余原版、快照、OptiFine Dim InstanceUseful As New List(Of McInstance) Dim InstanceRubbish As New List(Of McInstance) - McInstanceFilter(InstanceList, {McInstanceState.Old}, InstanceRubbish) + McInstanceFilter(InstanceList, {McInstance.McInstanceState.Old}, InstanceRubbish) '确认最新版本,若为快照则加入常用列表 Dim LargestInstance As McInstance = InstanceList. - Where(Function(v) v.State = McInstanceState.Original OrElse v.State = McInstanceState.Snapshot). + Where(Function(v) v.State = McInstance.McInstanceState.Original OrElse v.State = McInstance.McInstanceState.Snapshot). MaxBy(Function(v) v.ReleaseTime) - If LargestInstance IsNot Nothing AndAlso LargestInstance.State = McInstanceState.Snapshot Then + If LargestInstance IsNot Nothing AndAlso LargestInstance.State = McInstance.McInstanceState.Snapshot Then InstanceUseful.Add(LargestInstance) InstanceList.Remove(LargestInstance) End If '将剩余的快照全部拖进不常用列表 - McInstanceFilter(InstanceList, {McInstanceState.Snapshot}, InstanceRubbish) + McInstanceFilter(InstanceList, {McInstance.McInstanceState.Snapshot}, InstanceRubbish) '获取每个 Drop 下最新的原版与 OptiFine Dim NewerInstance As New Dictionary(Of String, McInstance) Dim ExistDrops As New List(Of Integer) For Each Instance As McInstance In InstanceList - If Not Instance.Version.Vaild Then Continue For + If Not Instance.Version.Valid Then Continue For If Not ExistDrops.Contains(Instance.Version.Drop) Then ExistDrops.Add(Instance.Version.Drop) Dim Key As String = Instance.Version.Drop & "-" & Instance.State If Not NewerInstance.ContainsKey(Key) Then NewerInstance.Add(Key, Instance) Continue For End If - If Instance.Version.HasOptiFine Then + If Instance.State = McInstance.McInstanceState.OptiFine Then If Instance.Version.OptiFineCode > NewerInstance(Key).Version.OptiFineCode Then NewerInstance(Key) = Instance 'OptiFine 根据版本号判断 Else If Instance.ReleaseTime > NewerInstance(Key).ReleaseTime Then NewerInstance(Key) = Instance '原版根据发布时间判断 @@ -1523,10 +1226,10 @@ OnLoaded: '将每个 Drop 下的最常规版本加入 For Each Drop As Integer In ExistDrops - If NewerInstance.ContainsKey(Drop & "-" & McInstanceState.OptiFine) AndAlso NewerInstance.ContainsKey(Drop & "-" & McInstanceState.Original) Then + If NewerInstance.ContainsKey(Drop & "-" & McInstance.McInstanceState.OptiFine) AndAlso NewerInstance.ContainsKey(Drop & "-" & McInstance.McInstanceState.Original) Then '同时存在 OptiFine 与原版 - Dim VanillaInstance As McInstance = NewerInstance(Drop & "-" & McInstanceState.Original) - Dim OptiFineInstance As McInstance = NewerInstance(Drop & "-" & McInstanceState.OptiFine) + Dim VanillaInstance As McInstance = NewerInstance(Drop & "-" & McInstance.McInstanceState.Original) + Dim OptiFineInstance As McInstance = NewerInstance(Drop & "-" & McInstance.McInstanceState.OptiFine) If VanillaInstance.Version.Drop > OptiFineInstance.Version.Drop Then '仅在原版比 OptiFine 更新时才加入原版 InstanceUseful.Add(VanillaInstance) @@ -1534,14 +1237,14 @@ OnLoaded: End If InstanceUseful.Add(OptiFineInstance) InstanceList.Remove(OptiFineInstance) - ElseIf NewerInstance.ContainsKey(Drop & "-" & McInstanceState.OptiFine) Then + ElseIf NewerInstance.ContainsKey(Drop & "-" & McInstance.McInstanceState.OptiFine) Then '没有原版,直接加入 OptiFine - InstanceUseful.Add(NewerInstance(Drop & "-" & McInstanceState.OptiFine)) - InstanceList.Remove(NewerInstance(Drop & "-" & McInstanceState.OptiFine)) - ElseIf NewerInstance.ContainsKey(Drop & "-" & McInstanceState.Original) Then + InstanceUseful.Add(NewerInstance(Drop & "-" & McInstance.McInstanceState.OptiFine)) + InstanceList.Remove(NewerInstance(Drop & "-" & McInstance.McInstanceState.OptiFine)) + ElseIf NewerInstance.ContainsKey(Drop & "-" & McInstance.McInstanceState.Original) Then '没有 OptiFine,直接加入原版 - InstanceUseful.Add(NewerInstance(Drop & "-" & McInstanceState.Original)) - InstanceList.Remove(NewerInstance(Drop & "-" & McInstanceState.Original)) + InstanceUseful.Add(NewerInstance(Drop & "-" & McInstance.McInstanceState.Original)) + InstanceList.Remove(NewerInstance(Drop & "-" & McInstance.McInstanceState.Original)) End If Next @@ -1588,11 +1291,11 @@ OnLoaded: If (Left.ReleaseTime.Year >= 2000 OrElse Right.ReleaseTime.Year >= 2000) AndAlso Left.ReleaseTime <> Right.ReleaseTime Then Return Left.ReleaseTime > Right.ReleaseTime '附加组件种类 - If Left.Version.HasFabric <> Right.Version.HasFabric Then Return Left.Version.HasFabric - If Left.Version.HasNeoForge <> Right.Version.HasNeoForge Then Return Left.Version.HasNeoForge - If Left.Version.HasForge <> Right.Version.HasForge Then Return Left.Version.HasForge + If Left.Version.ModLoaders.HasFlagF(ModLoaders.Fabric) <> Right.Version.ModLoaders.HasFlagF(ModLoaders.Fabric) Then Return Left.Version.ModLoaders.HasFlagF(ModLoaders.Fabric) + If Left.Version.ModLoaders.HasFlagF(ModLoaders.NeoForge) <> Right.Version.ModLoaders.HasFlagF(ModLoaders.NeoForge) Then Return Left.Version.ModLoaders.HasFlagF(ModLoaders.NeoForge) + If Left.Version.ModLoaders.HasFlagF(ModLoaders.Forge) <> Right.Version.ModLoaders.HasFlagF(ModLoaders.Forge) Then Return Left.Version.ModLoaders.HasFlagF(ModLoaders.Forge) If Left.Version.HasOptiFine <> Right.Version.HasOptiFine Then Return Left.Version.HasOptiFine - If Left.Version.HasLiteLoader <> Right.Version.HasLiteLoader Then Return Left.Version.HasLiteLoader + If Left.Version.ModLoaders.HasFlagF(ModLoaders.LiteLoader) <> Right.Version.ModLoaders.HasFlagF(ModLoaders.LiteLoader) Then Return Left.Version.ModLoaders.HasFlagF(ModLoaders.LiteLoader) '附加组件版本 If GetComponentCode(Left) <> GetComponentCode(Right) Then Return GetComponentCode(Left) > GetComponentCode(Right) '名称 @@ -1612,6 +1315,7 @@ OnLoaded: WriteIni(Folder & "PCL.ini", "CardValue" & (i + 1), Value) Next #End Region + Return Results End Function ''' @@ -1620,7 +1324,7 @@ OnLoaded: ''' 用于筛选的列表。 ''' 需要筛选出的版本类型。-2 代表隐藏的版本。 ''' 卡片的名称。 - Private Sub McInstanceFilter(ByRef InstanceList As List(Of McInstance), ByRef Target As Dictionary(Of McInstanceCardType, List(Of McInstance)), Formula As McInstanceState(), CardType As McInstanceCardType) + Private Sub McInstanceFilter(ByRef InstanceList As List(Of McInstance), ByRef Target As Dictionary(Of McInstanceCardType, List(Of McInstance)), Formula As McInstance.McInstanceState(), CardType As McInstanceCardType) Dim KeepList = InstanceList.Where(Function(v) Formula.Contains(v.State)).ToList '加入版本列表,并从剩余中删除 If KeepList.Any Then @@ -1634,7 +1338,7 @@ OnLoaded: ''' 用于筛选的列表。 ''' 需要筛选出的版本类型。-2 代表隐藏的版本。 ''' 传入需要增加入的列表。 - Private Sub McInstanceFilter(ByRef InstanceList As List(Of McInstance), Formula As McInstanceState(), ByRef KeepList As List(Of McInstance)) + Private Sub McInstanceFilter(ByRef InstanceList As List(Of McInstance), Formula As McInstance.McInstanceState(), ByRef KeepList As List(Of McInstance)) KeepList.AddRange(InstanceList.Where(Function(v) Formula.Contains(v.State))) '加入版本列表,并从剩余中删除 If KeepList.Any Then @@ -1909,7 +1613,7 @@ OnLoaded: '根据 Inherit 获取最深层版本 Dim OriginalInstance As McInstance = Instance '1.17+ 的 Forge 不寻找 Inherit - If Not (Instance.Version.HasForgelike AndAlso Instance.Version.Vanilla.Major >= 17) Then + If Not ((Instance.Version.ModLoaders.HasFlagF(ModLoaders.Forge) OrElse Instance.Version.ModLoaders.HasFlagF(ModLoaders.NeoForge)) AndAlso Instance.Version.Vanilla.Major >= 17) Then Do Until OriginalInstance.InheritName = "" If OriginalInstance.InheritName = OriginalInstance.Name Then Exit Do OriginalInstance = New McInstance(McFolderSelected & "versions\" & OriginalInstance.InheritName & "\") @@ -2045,7 +1749,7 @@ OnLoaded: ResultArray.Add(Key & GetUuid(), BasicArray(i)) Else Logger.Info($"发现重复的支持库:{BasicArray(i)} ({BasicArrayVersion}) 与 {ResultArray(Key)} ({ResultArrayVersion}),已忽略其中之一") - If CompareVersionGE(BasicArrayVersion, ResultArrayVersion) Then + If McUtils.CompareVersionGE(BasicArrayVersion, ResultArrayVersion) Then ResultArray(Key) = BasicArray(i) End If End If @@ -2415,82 +2119,6 @@ OnLoaded: End Try End Sub - ''' - ''' 比较两个版本名;等同 Left >= Right。 - ''' 无法比较两个预发布版的大小。 - ''' 支持的格式:未知版本, 1.13.2, 1.7.10-pre4, 1.8_pre, 1.14 Pre-Release 2, 1.14.4 C6 - ''' - Public Function CompareVersionGE(Left As String, Right As String) As Boolean - Return CompareVersion(Left, Right) >= 0 - End Function - ''' - ''' 比较两个版本名,若 Left 较新则返回 1,相同则返回 0,Right 较新则返回 -1;等同 Left - Right。 - ''' 无法比较两个预发布版的大小。 - ''' 支持的格式:未知版本, 26.1-snapshot-1,1.13.2, 1.7.10-pre4, 1.8_pre, 1.14 Pre-Release 2, 1.14.4 C6 - ''' - Public Function CompareVersion(Left As String, Right As String) As Integer - If Left = "未知版本" OrElse Right = "未知版本" Then - If Left = "未知版本" AndAlso Right <> "未知版本" Then Return 1 - If Left = "未知版本" AndAlso Right = "未知版本" Then Return 0 - If Left <> "未知版本" AndAlso Right = "未知版本" Then Return -1 - End If - Left = Left.Lower - Right = Right.Lower - Dim Lefts = Left.Replace("快照", "snapshot").Replace("预览版", "pre").RegexSearch("[a-z]+|[0-9]+").ToList - Dim Rights = Right.Replace("快照", "snapshot").Replace("预览版", "pre").RegexSearch("[a-z]+|[0-9]+").ToList - Dim i As Integer = 0 - While True - '两边均缺失,感觉是一个东西 - If Lefts.Count - 1 < i AndAlso Rights.Count - 1 < i Then - If Left > Right Then Return 1 - If Left < Right Then Return -1 - Return 0 - End If - '确定两边的数值 - Dim LeftValue As String = If(Lefts.Count - 1 < i, 0, Lefts(i)) - Dim RightValue As String = If(Rights.Count - 1 < i, 0, Rights(i)) - If LeftValue = RightValue Then GoTo NextEntry - If LeftValue = "rc" Then LeftValue = -1 - If LeftValue = "pre" Then LeftValue = -2 - If LeftValue = "snapshot" Then LeftValue = -3 - If LeftValue = "experimental" Then LeftValue = -4 - Dim LeftValValue = Val(LeftValue) - If RightValue = "rc" Then RightValue = -1 - If RightValue = "pre" Then RightValue = -2 - If RightValue = "snapshot" Then RightValue = -3 - If RightValue = "experimental" Then RightValue = -4 - Dim RightValValue = Val(RightValue) - If LeftValValue = 0 AndAlso RightValValue = 0 Then - '如果没有数值则直接比较字符串 - If LeftValue > RightValue Then - Return 1 - ElseIf LeftValue < RightValue Then - Return -1 - End If - Else - '如果有数值则比较数值 - '这会使得一边是数字一边是字母时数字方更大 - If LeftValValue > RightValValue Then - Return 1 - ElseIf LeftValValue < RightValValue Then - Return -1 - End If - End If -NextEntry: - i += 1 - End While - Return 0 - End Function - ''' - ''' 比较两个版本名的排序器。 - ''' - Public Class VersionComparer - Implements IComparer(Of String) - Public Function Compare(x As String, y As String) As Integer Implements IComparer(Of String).Compare - Return CompareVersion(x, y) - End Function - End Class - ''' ''' 打码字符串中的 AccessToken。 ''' @@ -2514,14 +2142,10 @@ NextEntry: ''' Public Function FilterUserName(Raw As String, FilterChar As Char) As String If Raw.Contains(":\Users\") Then - For Each Token In Raw.RegexSearch("(?<=:\\Users\\)[^\\]+") - Raw = Raw.Replace("\Users\" & Token, "\Users\" & New String(FilterChar, Token.Count)) - Next + Raw = RegularExpressions.Regex.Replace(Raw, "(?<=:\\Users\\)[^\\]+", Function(Token) New String(FilterChar, Token.Length)) End If If Raw.Contains(":/Users/") Then - For Each Token In Raw.RegexSearch("(?<=:/Users/)[^/]+") - Raw = Raw.Replace("/Users/" & Token, "/Users/" & New String(FilterChar, Token.Count)) - Next + Raw = RegularExpressions.Regex.Replace(Raw, "(?<=:/Users/)[^/]+", Function(Token) New String(FilterChar, Token.Length)) End If Return Raw End Function diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb index 5b86369..eb55c72 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb @@ -74,7 +74,7 @@ Public Module ModModpack Catch ex As Exception If ex.GetDisplay(True).Contains("Error.WinIOError") Then Throw New Exception("打开整合包文件失败", ex) - ElseIf File.EndsWithF(".rar", True) Then + ElseIf PathUtils.GetExtension(File) = "rar" Then Throw New Exception("PCL 无法处理 rar 格式的压缩包,请在解压后重新压缩为 zip 格式再试", ex) Else Throw New Exception("打开整合包文件失败,文件可能损坏或为不支持的压缩包格式", ex) @@ -119,8 +119,8 @@ Public Module ModModpack ''' 对路径末尾是否为 \ 没有要求。 ''' Private Sub CopyOverrideDirectory(OverridesFolder As String, VersionFolder As String, Loader As LoaderBase) - If Not OverridesFolder.EndsWithF("\") Then OverridesFolder += "\" - If Not VersionFolder.EndsWithF("\") Then VersionFolder += "\" + OverridesFolder = PathUtils.AddSlashSuffix(OverridesFolder) + VersionFolder = PathUtils.AddSlashSuffix(VersionFolder) '复制文件 If DirectoryUtils.Exists(OverridesFolder) Then Logger.Info($"处理整合包覆写文件夹:{OverridesFolder} → {VersionFolder}") @@ -174,25 +174,25 @@ Public Module ModModpack If InstanceName = "" Then InstanceName = MyMsgBoxInput("输入版本名称", "", "", New Collection(Of Validate) From {Validate}) If String.IsNullOrEmpty(InstanceName) Then Throw New OperationCanceledException End If + Dim VersionFolder As String = PathUtils.AddSlashSuffix(Path.Combine(McFolderSelected, "versions", InstanceName) & "\") '获取 Mod API 版本信息 - Dim TargetVersion As New McVersion - TargetVersion.VanillaName = Json("minecraft")("version").ToString + Dim TargetVersion As New McVersion(Json("minecraft")("version").ToString) For Each Entry In If(Json("minecraft")("modLoaders"), {}) Dim Id As String = If(Entry("id"), "").ToString.Lower If Id.StartsWithF("forge-") Then 'Forge 指定 If Id.Contains("recommended") Then Throw New Exception("该整合包版本过老,已不支持进行安装!") Logger.Info($"整合包 Forge 版本:{Id}") - TargetVersion.Forge = Id.Replace("forge-", "") + TargetVersion.ModLoaderVersions(ModLoaders.Forge) = Id.Replace("forge-", "") ElseIf Id.StartsWithF("neoforge-") Then 'NeoForge 指定 Logger.Info($"整合包 NeoForge 版本:{Id}") - TargetVersion.NeoForge = Id.Replace("neoforge-", "") + TargetVersion.ModLoaderVersions(ModLoaders.NeoForge) = Id.Replace("neoforge-", "") ElseIf Id.StartsWithF("fabric-") Then 'Fabric 指定 Logger.Info($"整合包 Fabric 版本:{Id}") - TargetVersion.Fabric = Id.Replace("fabric-", "") + TargetVersion.ModLoaderVersions(ModLoaders.Fabric) = Id.Replace("fabric-", "") Else NotifyIncompatibleLoader(Id) End If @@ -202,12 +202,12 @@ Public Module ModModpack Dim InstallLoaders As New List(Of LoaderBase) Dim OverrideHome As String = If(Json("overrides"), "") If OverrideHome <> "" Then + Dim OverridesFolder As String = InstallTemp & ArchiveBaseFolder & If(OverrideHome = "." OrElse OverrideHome = "./", "", OverrideHome) '#5613 + If Not PathUtils.IsParentOf(InstallTemp, OverridesFolder) Then Throw New Exception("整合包的 overrides 路径超出了解压目录,请向整合包作者反馈此问题!") InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件", Sub(Task As LoaderTask(Of String, Integer)) ExtractModpackFiles(InstallTemp, FileAddress, Task.CreateCancellationToken(), Task.CreateSyncProgressProvider(ToPercentage:=0.6)) - CopyOverrideDirectory( - InstallTemp & ArchiveBaseFolder & If(OverrideHome = "." OrElse OverrideHome = "./", "", OverrideHome), '#5613 - $"{McFolderSelected}versions\{InstanceName}", Task) + CopyOverrideDirectory(OverridesFolder, VersionFolder, Task) End Sub) With { .ProgressWeight = FileUtils.GetInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s End If @@ -293,7 +293,7 @@ Public Module ModModpack Dim File = ResourceVersion.FromPlatformJson(ModJson, Type) If Not File.DownloadAvailable Then Continue For '实际的添加 - FileList.Add(Id, File.ToNetFile($"{McFolderSelected}versions\{InstanceName}\{TargetFolder}\", + FileList.Add(Id, File.ToNetFile(PathUtils.AddSlashSuffix(Path.Combine(VersionFolder, TargetFolder)), ResourceVersion.DownloadReason.ModPack, TargetVersion.VanillaName, TargetVersion.ModLoaders)) Task.Progress += 1 / (1 + ModList.Count) Next @@ -307,15 +307,9 @@ Public Module ModModpack End If '构造加载器 - Dim Request As New McInstallRequest With { - .NewInstanceName = InstanceName, - .VersionFolder = $"{McFolderSelected}versions\{InstanceName}\", - .MinecraftName = TargetVersion.VanillaName, - .ForgeVersion = TargetVersion.Forge, - .NeoForgeVersion = TargetVersion.NeoForge, - .FabricVersion = TargetVersion.Fabric - } - Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request) + Dim Target As New McComponents(TargetVersion.VanillaName, TargetVersion.ModLoaderVersions) + Dim MergeLoaders = McInstallLoadersGet(New McInstallRequest With { + .NewInstanceName = InstanceName, .InstanceFolder = VersionFolder, .Components = Target}) '构造总加载器 Dim Loaders As New List(Of LoaderBase) Loaders.Add(New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .Block = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)}) @@ -323,7 +317,6 @@ Public Module ModModpack Loaders.Add(New LoaderTask(Of String, String)("最终整理文件", Sub(Task As LoaderTask(Of String, String)) '设置图标 - Dim VersionFolder As String = $"{McFolderSelected}versions\{InstanceName}\" If Logo IsNot Nothing AndAlso FileUtils.Exists(Logo) Then FileUtils.Copy(Logo, VersionFolder & "PCL\Logo.png") WriteIni(VersionFolder & "PCL\Setup.ini", "Logo", "PCL\Logo.png") @@ -337,10 +330,10 @@ Public Module ModModpack FileUtils.Delete(MapFile) Next '删除原始整合包文件 - For Each Target As String In {VersionFolder & "原始整合包.zip", VersionFolder & "原始整合包.mrpack"} - If FileUtils.Exists(Target) Then - Logger.Info($"删除原始整合包文件:{Target}") - FileUtils.Delete(Target) + For Each FileToDelete As String In {VersionFolder & "原始整合包.zip", VersionFolder & "原始整合包.mrpack"} + If FileUtils.Exists(FileToDelete) Then + Logger.Info($"删除原始整合包文件:{FileToDelete}") + FileUtils.Delete(FileToDelete) End If Next If FileUtils.Exists(FileAddress) AndAlso PathUtils.GetFileNameWithoutExtension(FileAddress) = "modpack" Then @@ -358,7 +351,7 @@ Public Module ModModpack '启动 Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState} - Loader.Start(Request.VersionFolder) + Loader.Start(VersionFolder) LoaderTaskbarAdd(Loader) FrmMain.BtnExtraDownload.ShowRefresh() Return Loader @@ -376,20 +369,21 @@ Public Module ModModpack End Try If Json("dependencies") Is Nothing OrElse Json("dependencies")("minecraft") Is Nothing Then Throw New Exception("Modrinth 整合包未提供 Minecraft 版本信息") '获取 Mod API 版本信息 - Dim TargetVersion As New McVersion + 'TODO: 改为使用类似 InstallPackMMC 中,一个版本名一个字典的方式来存储它,不要用 Unknown + Dim TargetVersion As New McVersion("Unknown") For Each Entry As JProperty In If(Json("dependencies"), {}) Select Case Entry.Name.Lower Case "minecraft" TargetVersion.VanillaName = Entry.Value.ToString Case "forge" 'eg. 14.23.5.2859 / 1.19-41.1.0 - TargetVersion.Forge = Entry.Value.ToString - Logger.Info($"整合包 Forge 版本:{TargetVersion.Forge}") + TargetVersion.ModLoaderVersions(ModLoaders.Forge) = Entry.Value.ToString + Logger.Info($"整合包 Forge 版本:{TargetVersion.ModLoaderVersions(ModLoaders.Forge)}") Case "neoforge", "neo-forge" 'eg. 20.6.98-beta - TargetVersion.NeoForge = Entry.Value.ToString - Logger.Info($"整合包 NeoForge 版本:{TargetVersion.NeoForge}") + TargetVersion.ModLoaderVersions(ModLoaders.NeoForge) = Entry.Value.ToString + Logger.Info($"整合包 NeoForge 版本:{TargetVersion.ModLoaderVersions(ModLoaders.NeoForge)}") Case "fabric-loader" 'eg. 0.14.14 - TargetVersion.Fabric = Entry.Value.ToString - Logger.Info($"整合包 Fabric 版本:{TargetVersion.Fabric}") + TargetVersion.ModLoaderVersions(ModLoaders.Fabric) = Entry.Value.ToString + Logger.Info($"整合包 Fabric 版本:{TargetVersion.ModLoaderVersions(ModLoaders.Fabric)}") Case Else NotifyIncompatibleLoader(Entry.Name) End Select @@ -402,18 +396,15 @@ Public Module ModModpack If InstanceName = "" Then InstanceName = MyMsgBoxInput("输入版本名称", "", "", New Collection(Of Validate) From {Validate}) If String.IsNullOrEmpty(InstanceName) Then Throw New OperationCanceledException End If + Dim VersionFolder As String = Path.Combine(McFolderSelected, "versions", InstanceName) & "\" '解压 Dim InstallTemp As String = RequestTaskTempFolder() Dim InstallLoaders As New List(Of LoaderBase) InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件", Sub(Task As LoaderTask(Of String, Integer)) ExtractModpackFiles(InstallTemp, FileAddress, Task.CreateCancellationToken(), Task.CreateSyncProgressProvider(ToPercentage:=0.5)) - CopyOverrideDirectory( - InstallTemp & ArchiveBaseFolder & "overrides", - McFolderSelected & "versions\" & InstanceName, Task) - CopyOverrideDirectory( - InstallTemp & ArchiveBaseFolder & "client-overrides", - McFolderSelected & "versions\" & InstanceName, Task) + CopyOverrideDirectory(InstallTemp & ArchiveBaseFolder & "overrides", VersionFolder, Task) + CopyOverrideDirectory(InstallTemp & ArchiveBaseFolder & "client-overrides", VersionFolder, Task) End Sub) With {.ProgressWeight = FileUtils.GetInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s '获取下载文件列表 Dim FileList As New List(Of NetFile) @@ -436,8 +427,8 @@ Public Module ModModpack Urls = ResourceVersion.ParseModrinthTrackArguments(Urls, ResourceVersion.DownloadReason.ModPack, TargetVersion.VanillaName, TargetVersion.ModLoaders).ToList 'Modrinth 来源追踪信息 Urls = Urls.Distinct.ToList() - Dim TargetPath As String = Path.Combine(McFolderSelected, "versions", InstanceName, File("path").ToString().TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)) - If Not PathUtils.IsParentOf($"{McFolderSelected}versions\{InstanceName}\", TargetPath) Then + Dim TargetPath As String = Path.Combine(VersionFolder, File("path").ToString().TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)) + If Not PathUtils.IsParentOf(VersionFolder, TargetPath) Then MyMsgBox($"整合包的文件路径超出了版本文件夹,请向整合包作者反馈此问题!{vbCrLf}目标:{Path.GetFullPath(TargetPath)}{vbCrLf}错误的文件:{TargetPath}", "文件路径校验失败", IsWarn:=True) Throw New OperationCanceledException End If @@ -449,15 +440,10 @@ Public Module ModModpack End If '构造加载器 - Dim Request As New McInstallRequest With { - .NewInstanceName = InstanceName, - .VersionFolder = $"{McFolderSelected}versions\{InstanceName}\", - .MinecraftName = TargetVersion.VanillaName, - .ForgeVersion = TargetVersion.Forge, - .NeoForgeVersion = TargetVersion.NeoForge, - .FabricVersion = TargetVersion.Fabric - } - Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request) + Dim Target As New McComponents(TargetVersion.VanillaName, TargetVersion.ModLoaderVersions) + Dim MergeLoaders = McInstallLoadersGet(New McInstallRequest With { + .NewInstanceName = InstanceName, .InstanceFolder = VersionFolder, + .Components = Target}) '构造总加载器 Dim Loaders As New List(Of LoaderBase) Loaders.Add(New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .Block = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)}) @@ -465,7 +451,6 @@ Public Module ModModpack Loaders.Add(New LoaderTask(Of String, String)("最终整理文件", Sub(Task As LoaderTask(Of String, String)) '设置图标 - Dim VersionFolder As String = $"{McFolderSelected}versions\{InstanceName}\" If Logo IsNot Nothing AndAlso FileUtils.Exists(Logo) Then FileUtils.Copy(Logo, VersionFolder & "PCL\Logo.png") WriteIni(VersionFolder & "PCL\Setup.ini", "Logo", "PCL\Logo.png") @@ -473,10 +458,10 @@ Public Module ModModpack Logger.Info($"已设置整合包 Logo:{Logo}") End If '删除原始整合包文件 - For Each Target As String In {VersionFolder & "原始整合包.zip", VersionFolder & "原始整合包.mrpack"} - If FileUtils.Exists(Target) Then - Logger.Info($"删除原始整合包文件:{Target}") - FileUtils.Delete(Target) + For Each FileToDelete As String In {VersionFolder & "原始整合包.zip", VersionFolder & "原始整合包.mrpack"} + If FileUtils.Exists(FileToDelete) Then + Logger.Info($"删除原始整合包文件:{FileToDelete}") + FileUtils.Delete(FileToDelete) End If Next If FileUtils.Exists(FileAddress) AndAlso PathUtils.GetFileNameWithoutExtension(FileAddress) = "modpack" Then @@ -494,7 +479,7 @@ Public Module ModModpack '启动 Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState} - Loader.Start(Request.VersionFolder) + Loader.Start(VersionFolder) LoaderTaskbarAdd(Loader) FrmMain.BtnExtraDownload.ShowRefresh() Return Loader @@ -515,24 +500,21 @@ Public Module ModModpack If Validate.Validate(InstanceName) <> "" Then InstanceName = "" If InstanceName = "" Then InstanceName = MyMsgBoxInput("输入版本名称", "", "", New Collection(Of Validate) From {Validate}) If String.IsNullOrEmpty(InstanceName) Then Throw New OperationCanceledException + Dim VersionFolder As String = Path.Combine(McFolderSelected, "versions", InstanceName) & "\" '解压 Dim InstallTemp As String = RequestTaskTempFolder() Dim InstallLoaders As New List(Of LoaderBase) InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件", Sub(Task As LoaderTask(Of String, Integer)) ExtractModpackFiles(InstallTemp, FileAddress, Task.CreateCancellationToken(), Task.CreateSyncProgressProvider(ToPercentage:=0.6)) - CopyOverrideDirectory( - InstallTemp & ArchiveBaseFolder & "minecraft", - McFolderSelected & "versions\" & InstanceName, Task) + CopyOverrideDirectory(InstallTemp & ArchiveBaseFolder & "minecraft", VersionFolder, Task) End Sub) With {.ProgressWeight = FileUtils.GetInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s '构造游戏本体安装加载器 If Json("gameVersion") Is Nothing Then Throw New Exception("该 HMCL 整合包未提供游戏版本信息,无法安装!") - Dim Request As New McInstallRequest With { - .NewInstanceName = InstanceName, - .VersionFolder = $"{McFolderSelected}versions\{InstanceName}\", - .MinecraftName = Json("gameVersion").ToString - } - Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request) + Dim Target As New McComponents(Json("gameVersion").ToString, New Dictionary(Of ModLoaders, String)) + Dim MergeLoaders = McInstallLoadersGet(New McInstallRequest With { + .NewInstanceName = InstanceName, .InstanceFolder = VersionFolder, + .Components = Target}) '构造总加载器 Dim Loaders As New List(Of LoaderBase) From { New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .Block = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)}, @@ -546,7 +528,7 @@ Public Module ModModpack End If '启动 Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState} - Loader.Start(Request.VersionFolder) + Loader.Start(VersionFolder) LoaderTaskbarAdd(Loader) FrmMain.BtnExtraDownload.ShowRefresh() Return Loader @@ -568,16 +550,22 @@ Public Module ModModpack If Validate.Validate(InstanceName) <> "" Then InstanceName = "" If InstanceName = "" Then InstanceName = MyMsgBoxInput("输入版本名称", "", "", New Collection(Of Validate) From {Validate}) If String.IsNullOrEmpty(InstanceName) Then Throw New OperationCanceledException + Dim VersionFolder As String = Path.Combine(McFolderSelected, "versions", InstanceName) & "\" '解压 Dim InstallTemp As String = RequestTaskTempFolder() - Dim SetupFile As String = $"{McFolderSelected}versions\{InstanceName}\PCL\Setup.ini" + Dim SetupFile As String = VersionFolder & "PCL\Setup.ini" Dim InstallLoaders As New List(Of LoaderBase) InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件", Sub(Task As LoaderTask(Of String, Integer)) ExtractModpackFiles(InstallTemp, FileAddress, Task.CreateCancellationToken(), Task.CreateSyncProgressProvider(ToPercentage:=0.55)) - CopyOverrideDirectory( - InstallTemp & ArchiveBaseFolder & ".minecraft", - McFolderSelected & "versions\" & InstanceName, Task) + 'Prism 优先使用 minecraft/ 而非 .minecraft/(#9272) + Dim MmcGameRoot As String + If DirectoryUtils.Exists(InstallTemp & ArchiveBaseFolder & ".minecraft") AndAlso Not DirectoryUtils.Exists(InstallTemp & ArchiveBaseFolder & "minecraft") Then + MmcGameRoot = InstallTemp & ArchiveBaseFolder & ".minecraft" + Else + MmcGameRoot = InstallTemp & ArchiveBaseFolder & "minecraft" + End If + CopyOverrideDirectory(MmcGameRoot, VersionFolder, Task) '读取 MMC 设置文件(#2655) Try Dim MmcSetupFile As String = InstallTemp & ArchiveBaseFolder & "instance.cfg" @@ -611,7 +599,7 @@ Public Module ModModpack If Logo <> "" AndAlso FileUtils.Exists($"{InstallTemp}{ArchiveBaseFolder}{Logo}.png") Then WriteIni(SetupFile, "LogoCustom", True) WriteIni(SetupFile, "Logo", "PCL\Logo.png") - FileUtils.Copy($"{InstallTemp}{ArchiveBaseFolder}{Logo}.png", $"{McFolderSelected}versions\{InstanceName}\PCL\Logo.png") + FileUtils.Copy($"{InstallTemp}{ArchiveBaseFolder}{Logo}.png", VersionFolder & "PCL\Logo.png") Logger.Info($"迁移 MultiMC 版本独立设置:版本图标({Logo}.png)") End If 'JVM 参数 @@ -633,19 +621,20 @@ Public Module ModModpack End Sub) With {.ProgressWeight = FileUtils.GetInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s '构造版本安装请求 If PackJson("components") Is Nothing Then Throw New Exception("该 MMC 整合包未提供游戏版本信息,无法安装!") - Dim Request As New McInstallRequest With {.NewInstanceName = InstanceName, .VersionFolder = $"{McFolderSelected}versions\{InstanceName}\"} + Dim MinecraftVersion As String = Nothing + Dim ModLoaderVersions As New Dictionary(Of ModLoaders, String) For Each Component As JObject In PackJson("components") If Not Component.ContainsKey("uid") Then Continue For Dim UID As String = Component("uid").ToString Select Case UID Case "net.minecraft" - Request.MinecraftName = Component("version") + MinecraftVersion = Component("version") Case "net.minecraftforge" - Request.ForgeVersion = Component("version") + ModLoaderVersions(ModLoaders.Forge) = Component("version") Case "net.neoforged" - Request.NeoForgeVersion = Component("version") + ModLoaderVersions(ModLoaders.NeoForge) = Component("version") Case "net.fabricmc.fabric-loader" - Request.FabricVersion = Component("version") + ModLoaderVersions(ModLoaders.Fabric) = Component("version") Case Else If UID.StartsWithF("org.lwjgl") Then '#8210 Logger.Info($"已跳过 LWJGL 项:{UID}") @@ -655,7 +644,10 @@ Public Module ModModpack End Select Next '构造加载器 - Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request) + Dim Target As New McComponents(MinecraftVersion, ModLoaderVersions) + Dim MergeLoaders = McInstallLoadersGet(New McInstallRequest With { + .NewInstanceName = InstanceName, .InstanceFolder = VersionFolder, + .Components = Target}) '构造总加载器 Dim Loaders As New List(Of LoaderBase) Loaders.Add(New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .Block = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)}) @@ -670,7 +662,7 @@ Public Module ModModpack '启动 Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState} - Loader.Start(Request.VersionFolder) + Loader.Start(VersionFolder) LoaderTaskbarAdd(Loader) FrmMain.BtnExtraDownload.ShowRefresh() Return Loader @@ -695,16 +687,15 @@ Public Module ModModpack If InstanceName = "" Then InstanceName = MyMsgBoxInput("输入版本名称", "", "", New Collection(Of Validate) From {Validate}) If String.IsNullOrEmpty(InstanceName) Then Throw New OperationCanceledException End If + Dim VersionFolder As String = Path.Combine(McFolderSelected, "versions", InstanceName) & "\" '解压 Dim InstallTemp As String = RequestTaskTempFolder() - Dim SetupFile As String = $"{McFolderSelected}versions\{InstanceName}\PCL\Setup.ini" + Dim SetupFile As String = VersionFolder & "PCL\Setup.ini" Dim InstallLoaders As New List(Of LoaderBase) InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件", Sub(Task As LoaderTask(Of String, Integer)) ExtractModpackFiles(InstallTemp, FileAddress, Task.CreateCancellationToken(), Task.CreateSyncProgressProvider(ToPercentage:=0.6)) - CopyOverrideDirectory( - InstallTemp & ArchiveBaseFolder & "overrides", - McFolderSelected & "versions\" & InstanceName, Task) + CopyOverrideDirectory(InstallTemp & ArchiveBaseFolder & "overrides", VersionFolder, Task) 'JVM 参数 If Json("launchInfo") IsNot Nothing Then Dim LaunchInfo As JObject = Json("launchInfo") @@ -723,16 +714,26 @@ Public Module ModModpack Hint("PCL 不支持安装 Quilt 整合包!", HintType.Red) Throw New OperationCanceledException End If - Dim Request As New McInstallRequest With { - .NewInstanceName = InstanceName, - .VersionFolder = $"{McFolderSelected}versions\{InstanceName}\", - .MinecraftName = Addons("game"), - .OptiFineVersion = If(Addons.ContainsKey("optifine"), Addons("optifine"), Nothing), - .ForgeVersion = If(Addons.ContainsKey("forge"), Addons("forge"), Nothing), - .NeoForgeVersion = If(Addons.ContainsKey("neoforge"), Addons("neoforge"), Nothing), - .FabricVersion = If(Addons.ContainsKey("fabric"), Addons("fabric"), Nothing) - } - Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request) + Dim ModLoaderVersions As New Dictionary(Of ModLoaders, String) + If Addons.ContainsKey("forge") Then ModLoaderVersions(ModLoaders.Forge) = Addons("forge") + If Addons.ContainsKey("neoforge") Then ModLoaderVersions(ModLoaders.NeoForge) = Addons("neoforge") + If Addons.ContainsKey("fabric") Then ModLoaderVersions(ModLoaders.Fabric) = Addons("fabric") + Dim OptiFineEntry As JObject = Nothing + If Addons.ContainsKey("optifine") Then + Dim MinecraftVersion = Addons("game") + If MinecraftVersion.EndsWithF(".0") Then MinecraftVersion = MinecraftVersion.Substring(0, MinecraftVersion.Length - 2) + Dim OptiFineFileSuffix = "_HD_" & Addons("optifine").AfterLast("HD_") & ".jar" + '从镜像源拉取 OptiFine 列表 + If DlOptiFineListBmclapiLoader.State <> LoadState.Finished Then DlOptiFineListBmclapiLoader.WaitForExit() + Dim Entry = DlOptiFineListBmclapiLoader.Output.Value.FirstOrDefault( + Function(v) v.Inherit = MinecraftVersion AndAlso v.FileName.EndsWithF(OptiFineFileSuffix)) + If Entry Is Nothing Then Throw New Exception($"OptiFine 列表中没有整合包所需的 OptiFine {Addons("optifine")}(Minecraft {Addons("game")})") + OptiFineEntry = JObject.FromObject(Entry) + End If + Dim Target As New McComponents(Addons("game"), ModLoaderVersions, OptiFineEntry) + Dim MergeLoaders = McInstallLoadersGet(New McInstallRequest With { + .NewInstanceName = InstanceName, .InstanceFolder = VersionFolder, + .Components = Target}) '构造总加载器 Dim Loaders As New List(Of LoaderBase) Loaders.Add(New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .Block = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)}) @@ -748,7 +749,7 @@ Public Module ModModpack '启动 Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState} 'If Archive IsNot Nothing Then Archive.Dispose() '解除占用,以免在加载器中触发 “正由另一进程使用,因此该进程无法访问此文件” - Loader.Start(Request.VersionFolder) + Loader.Start(VersionFolder) LoaderTaskbarAdd(Loader) FrmMain.BtnExtraDownload.ShowRefresh() Return Loader diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb index 3bd883c..15d373d 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb @@ -319,6 +319,8 @@ Public Module ModWatcher GetClassName(hwnd, str, str.Capacity) Dim ClassName As String = str.ToString If Not (ClassName = "GLFW30" OrElse ClassName = "SDL_app" OrElse ClassName = "LWJGL" OrElse ClassName = "SunAwtFrame") Then Return + 'SDL 会先创建隐藏窗口,待初始化完成后才显示(#9073) + If ClassName = "SDL_app" AndAlso Not IsWindowVisible(hwnd) Then Return '获取窗口标题名 str = New StringBuilder(512) GetWindowText(hwnd, str, str.Capacity) @@ -347,6 +349,7 @@ Public Module ModWatcher Private Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (hWnd As Integer, str As StringBuilder, maxCount As Integer) As Integer Private Declare Function SetWindowText Lib "user32" Alias "SetWindowTextA" (hWnd As Integer, str As String) As Boolean Private Declare Function ShowWindow Lib "user32" (hWnd As IntPtr, cmdWindow As UInteger) As Boolean + Private Declare Function IsWindowVisible Lib "user32" (hWnd As IntPtr) As Boolean Private Declare Function GetWindowThreadProcessId Lib "user32" (hWnd As IntPtr, ByRef lpdwProcessId As Integer) As Integer '崩溃处理 diff --git a/Plain Craft Launcher 2/Modules/ModEvent.vb b/Plain Craft Launcher 2/Modules/ModEvent.vb index 5565e72..5db54c4 100644 --- a/Plain Craft Launcher 2/Modules/ModEvent.vb +++ b/Plain Craft Launcher 2/Modules/ModEvent.vb @@ -1,6 +1,9 @@ + #Region "附加属性" +Imports PCL.FormMain + ''' ''' 用于在 XAML 中初始化列表对象。 ''' 附加属性无法在 XAML 中为每个对象初始化独立的列表对象,因此需要一个包装类,然后在 XAML 中显式初始化。 @@ -272,9 +275,23 @@ Public Class CustomEvent Hint(Arg0.Replace("\n", vbCrLf), If(Args.Length = 1, HintType.Blue, EnumUtils.FromString(Of HintType)(Arg1))) Case EventType.切换页面 - RunInUi(Sub() FrmMain.PageChange( - EnumUtils.FromString(Of FormMain.PageType)(Arg0), - If(Args.Length = 1, FormMain.PageSubType.Default, EnumUtils.FromString(Of FormMain.PageSubType)(Arg1)))) + RunInUi( + Sub() + Dim TargetPage = EnumUtils.FromString(Of PageType)(Arg0) + If TargetPage = PageType.InstanceSetup Then + If McInstanceSelected Is Nothing Then + Hint("请先选择一个 Minecraft 版本!", HintType.Red) + Return + End If + If McInstanceLock.IsLocked(McInstanceSelected) Then + Hint("该版本正在被修改,暂时无法进入版本设置!", HintType.Red) + Return + End If + PageInstanceLeft.Instance = McInstanceSelected + End If + FrmMain.PageChange(TargetPage, + If(Args.Length = 1, PageSubType.Default, EnumUtils.FromString(Of PageSubType)(Arg1))) + End Sub) Case EventType.导入整合包, EventType.安装整合包 RunInUi(Sub() ModpackInstall()) @@ -295,7 +312,7 @@ Public Class CustomEvent Case 2 PageOtherTest.StartCustomDownload(Arg0, Arg1) Case Else - PageOtherTest.StartCustomDownload(Arg0, Arg(1), Args(2)) + PageOtherTest.StartCustomDownload(Arg0, Args(1), Args(2)) End Select Catch PageOtherTest.StartCustomDownload(Arg0, "未知") @@ -304,16 +321,16 @@ Public Class CustomEvent Case EventType.修改设置, EventType.写入设置 If Args.Length = 1 Then Throw New Exception($"EventType {Type} 需要至少 2 个以 | 分割的参数,例如 UiLauncherTransparent|400") - Settings.SetSafe(Arg0, Arg1, Instance:=McInstanceSelected) - If Args.Length = 2 Then Hint($"已写入设置:{Arg0} → {Arg1}", HintType.Green) + Settings.SetSafe(Arg0, Args(1), Instance:=McInstanceSelected) + If Args.Length = 2 Then Hint($"已写入设置:{Arg0} → {Args(1)}", HintType.Green) Case EventType.修改变量, EventType.写入变量 If Args.Length = 1 Then Throw New Exception($"EventType {Type} 需要至少 2 个以 | 分割的参数,例如 VariableName|SomeValue") Try - RegistryUtils.Write($"HKEY_CURRENT_USER\Software\{RegFolder}", $"CustomEvent{Arg0}", Arg1) - If Args.Length = 2 Then Hint($"已写入变量:{Arg0} → {Arg1}", HintType.Green) + RegistryUtils.Write($"HKEY_CURRENT_USER\Software\{RegFolder}", $"CustomEvent{Arg0}", Args(1)) + If Args.Length = 2 Then Hint($"已写入变量:{Arg0} → {Args(1)}", HintType.Green) Catch ex As Exception - Logger.Warn(ex, $"写入变量失败:{Arg0} → {Arg1}", LogBehavior.Toast) + Logger.Warn(ex, $"写入变量失败:{Arg0} → {Args(1)}", LogBehavior.Toast) End Try Case EventType.加入房间 @@ -353,7 +370,7 @@ Public Class CustomEvent Dim RawFileName As String Try RawFileName = PathUtils.GetLastPart(RelativeUrl) - If Not RawFileName.EndsWithF(".json", True) Then Throw New Exception("未指向 .json 后缀的文件") + If Not PathUtils.GetExtension(RawFileName) = "json" Then Throw New Exception("未指向 .json 后缀的文件") Catch ex As Exception Throw New Exception("联网帮助页面须指向一个帮助 JSON 文件,并在同路径下包含相应 XAML 文件!" & vbCrLf & "例如:" & vbCrLf & diff --git a/Plain Craft Launcher 2/Modules/ModMain.vb b/Plain Craft Launcher 2/Modules/ModMain.vb index ff4b31a..529ac16 100644 --- a/Plain Craft Launcher 2/Modules/ModMain.vb +++ b/Plain Craft Launcher 2/Modules/ModMain.vb @@ -414,6 +414,7 @@ EndHint: Public FrmInstanceModDisabled As PageInstanceModDisabled Public FrmInstanceSetup As PageInstanceSetup Public FrmInstanceExport As PageInstanceExport + Public FrmDownloadModify As PageDownloadModify '资源信息分页声明 Public FrmDownloadResourceDetail As PageDownloadResourceDetail @@ -510,7 +511,7 @@ EndHint: EventData = If(JsonData("EventData"), "") IsEvent = True Else - Dim XamlAddress As String = FilePath.Lower.Replace(".json", ".xaml") + Dim XamlAddress As String = Path.ChangeExtension(FilePath, ".xaml") If FileUtils.Exists(XamlAddress) Then XamlContent = FileUtils.ReadAsString(XamlAddress) IsEvent = False @@ -596,9 +597,9 @@ EndHint: '读取自带文件 For Each File In DirectoryUtils.EnumerateFiles(PathTemp & "Help", True) '跳过非 json 文件与以 . 开头的文件夹 - If PathUtils.RemoveLastPart(File).Replace(PathTemp & "Help", "").Contains("\.") OrElse PathUtils.GetExtension(File) <> "json" Then Continue For + If PathUtils.RemoveLastPart(File).Substring((PathTemp & "Help").Length).Contains("\.") OrElse PathUtils.GetExtension(File) <> "json" Then Continue For '检查忽略列表 - Dim RealPath As String = File.Replace(PathTemp & "Help\", "") + Dim RealPath As String = File.Substring((PathTemp & "Help\").Length) For Each Ignore In IgnoreList If RealPath.RegexCheck(Ignore) Then Logger.Trace($"已忽略 {RealPath}:{Ignore}") @@ -857,10 +858,10 @@ NextFile: Else Text = Text.Replace("{version}", Replacer(McInstanceSelected.Version.VanillaName)) End If - Text = Text.Replace("{forge}", Replacer(If(McInstanceSelected.Version.HasForge, McInstanceSelected.Version.Forge, ""))) - Text = Text.Replace("{fabric}", Replacer(If(McInstanceSelected.Version.HasFabric, McInstanceSelected.Version.Fabric, ""))) - Text = Text.Replace("{neoforge}", Replacer(If(McInstanceSelected.Version.HasNeoForge, McInstanceSelected.Version.NeoForge, ""))) - Text = Text.Replace("{liteloader}", Replacer(If(McInstanceSelected.Version.HasLiteLoader, "true", ""))) + Text = Text.Replace("{forge}", Replacer(If(McInstanceSelected.Version.ModLoaders.HasFlagF(ModLoaders.Forge), McInstanceSelected.Version.ModLoaderVersions(ModLoaders.Forge), ""))) + Text = Text.Replace("{fabric}", Replacer(If(McInstanceSelected.Version.ModLoaders.HasFlagF(ModLoaders.Fabric), McInstanceSelected.Version.ModLoaderVersions(ModLoaders.Fabric), ""))) + Text = Text.Replace("{neoforge}", Replacer(If(McInstanceSelected.Version.ModLoaders.HasFlagF(ModLoaders.NeoForge), McInstanceSelected.Version.ModLoaderVersions(ModLoaders.NeoForge), ""))) + Text = Text.Replace("{liteloader}", Replacer(If(McInstanceSelected.Version.ModLoaders.HasFlagF(ModLoaders.LiteLoader), "true", ""))) Else Text = Text.Replace("{version_path}", Replacer(Nothing)) : Text = Text.Replace("{verpath}", Replacer(Nothing)) Text = Text.Replace("{version_indie}", Replacer(Nothing)) : Text = Text.Replace("{verindie}", Replacer(Nothing)) diff --git a/Plain Craft Launcher 2/Modules/ModMusic.vb b/Plain Craft Launcher 2/Modules/ModMusic.vb index 871970d..a3d54c9 100644 --- a/Plain Craft Launcher 2/Modules/ModMusic.vb +++ b/Plain Craft Launcher 2/Modules/ModMusic.vb @@ -320,7 +320,7 @@ Public Module ModMusic Hint("由于音频设备变更,音乐播放功能在重启 PCL 后才能恢复!", HintType.Red) Thread.Sleep(1000000000) End If - If Not (MusicCurrent.EndsWithF(".wav", True) OrElse MusicCurrent.EndsWithF(".mp3", True) OrElse MusicCurrent.EndsWithF(".flac", True)) OrElse + If Not ({"wav", "mp3", "flac"}.Contains(PathUtils.GetExtension(MusicCurrent))) OrElse ex.Message.Contains("0xC00D36C4") Then '#5096:不支持给定的 URL 的字节流类型。 (异常来自 HRESULT:0xC00D36C4) Hint("播放音乐失败(" & PathUtils.GetLastPart(MusicCurrent) & "):PCL 可能不支持此音乐格式,请将格式转换为 .wav、.mp3 或 .flac 后再试", HintType.Red) Else diff --git a/Plain Craft Launcher 2/Modules/Resource/LocalResourceFile.vb b/Plain Craft Launcher 2/Modules/Resource/LocalResourceFile.vb index 2d22d88..b48bead 100644 --- a/Plain Craft Launcher 2/Modules/Resource/LocalResourceFile.vb +++ b/Plain Craft Launcher 2/Modules/Resource/LocalResourceFile.vb @@ -25,7 +25,7 @@ Public Class LocalResourceFile End Get End Property ''' - ''' 假若该资源未被禁用时,文件的文件名。 + ''' 假若该资源未被禁用时,文件的文件名。包含 .jar/.litemod 等后缀。 ''' Public ReadOnly Property EnabledName As String Get @@ -57,7 +57,7 @@ Public Class LocalResourceFile Public Property Display As String Get If _Display Is Nothing Then _Display = ProjectVersion?.Display - If _Display Is Nothing Then _Display = PathUtils.GetFileNameWithoutExtension(File.FullName) + If _Display Is Nothing Then _Display = PathUtils.GetFileNameWithoutExtension(EnabledName) Return _Display End Get Set(value As String) @@ -238,7 +238,7 @@ Public Class LocalResourceFile Private _CurseForgeHash As UInteger? ''' - ''' 获取用于 Modrinth 信息获取的 Hash 值(SHA1)。 + ''' 获取用于 Modrinth 信息获取的 Hash 值(SHA1)。固定为小写。 ''' Public ReadOnly Property ModrinthHash As String Get @@ -269,7 +269,7 @@ Public Class LocalResourceFile End Function Public Overrides Function Equals(obj As Object) As Boolean Dim Target = TryCast(obj, LocalResourceFile) - Return Target IsNot Nothing AndAlso File.FullName = Target.File.FullName + Return Target IsNot Nothing AndAlso PathUtils.IsSame(File.FullName, Target.File.FullName) End Function #End Region diff --git a/Plain Craft Launcher 2/Modules/Resource/LocalResourceLoaders.vb b/Plain Craft Launcher 2/Modules/Resource/LocalResourceLoaders.vb index 4bed629..a36c715 100644 --- a/Plain Craft Launcher 2/Modules/Resource/LocalResourceLoaders.vb +++ b/Plain Craft Launcher 2/Modules/Resource/LocalResourceLoaders.vb @@ -24,11 +24,10 @@ Public Module LocalResourceLoaders '获取 Mod 文件夹下的可用文件列表 Dim ModFileList As New List(Of String) If DirectoryUtils.Exists(Loader.Input) Then - Dim RawName As String = Loader.Input.Lower For Each File In DirectoryUtils.EnumerateFiles(Loader.Input, True) - If File.BeforeLast("\").Lower & "\" <> RawName Then + If Not PathUtils.IsSame(PathUtils.RemoveLastPart(File), Loader.Input) Then '仅当 Forge 1.13- 且文件夹名与版本号相同时,才加载该子文件夹下的 Mod - If Not (PageInstanceLeft.Instance IsNot Nothing AndAlso PageInstanceLeft.Instance.Version.HasForge AndAlso + If Not (PageInstanceLeft.Instance IsNot Nothing AndAlso PageInstanceLeft.Instance.Version.ModLoaders.HasFlagF(ModLoaders.Forge) AndAlso PageInstanceLeft.Instance.Version.Vanilla.Major < 13 AndAlso PathUtils.GetLastPart(PathUtils.RemoveLastPart(File)) = $"1.{PageInstanceLeft.Instance.Version.Vanilla.Major}.{PageInstanceLeft.Instance.Version.Vanilla.Build}") Then Continue For @@ -36,12 +35,9 @@ Public Module LocalResourceLoaders End If Static IsModFile As Func(Of String, Boolean) = Function(FullName As String) - If FullName Is Nothing OrElse Not FullName.Contains(".") Then Return False - FullName = FullName.Lower - If FullName.EndsWithF(".jar", True) OrElse FullName.EndsWithF(".zip", True) OrElse FullName.EndsWithF(".litemod", True) OrElse - FullName.EndsWithF(".jar.disabled", True) OrElse FullName.EndsWithF(".zip.disabled", True) OrElse FullName.EndsWithF(".litemod.disabled", True) OrElse - FullName.EndsWithF(".jar.old", True) OrElse FullName.EndsWithF(".zip.old", True) OrElse FullName.EndsWithF(".litemod.old", True) Then Return True - Return False + If FullName Is Nothing Then Return False + If {"disabled", "old"}.Contains(PathUtils.GetExtension(FullName)) Then FullName = PathUtils.GetFileNameWithoutExtension(FullName) + Return {"jar", "zip", "litemod"}.Contains(PathUtils.GetExtension(FullName)) End Function If IsModFile(File) Then ModFileList.Add(File) Next @@ -271,7 +267,7 @@ Public Module LocalResourceLoaders Dim EntryVersion As String = Entry("gameVersion") Return EntryVersion IsNot Nothing AndAlso EntryVersion = VanillaVersion End Function). - MaxByAll(Function(Entry) Entry("gameVersion").ToString, New VersionComparer) '只保留最新的 MC 版本 + MaxByAll(Function(Entry) Entry("gameVersion").ToString, Comparer(Of String).Create(Function(x, y) McUtils.CompareVersion(x, y))) '只保留最新的 MC 版本 For Each Entry In LatestFiles Dim FileId = Entry("fileId").ToObject(Of Integer) If Not UpdateFileIds.ContainsKey(FileId) Then UpdateFileIds(FileId) = New List(Of LocalResourceFile) diff --git a/Plain Craft Launcher 2/Modules/Resource/ResourceProject.vb b/Plain Craft Launcher 2/Modules/Resource/ResourceProject.vb index 53e70d9..b2deefb 100644 --- a/Plain Craft Launcher 2/Modules/Resource/ResourceProject.vb +++ b/Plain Craft Launcher 2/Modules/Resource/ResourceProject.vb @@ -184,11 +184,11 @@ Public Class ResourceProject If Not NewFile.DownloadAvailable Then Continue For ModLoaders = ModLoaders Or NewFile.ModLoaders Dim GameVersions = File("gameVersions").ToObject(Of List(Of String)) - If Not GameVersions.Any(Function(v) McVersion.IsFormatFit(v)) Then Continue For + If Not GameVersions.Any(Function(v) McVersion.IsStandardFormat(v)) Then Continue For Files.Add(New KeyValuePair(Of Integer, List(Of String))(File("id"), GameVersions)) Next For Each File In If(Data("latestFilesIndexes"), New JArray) '这俩玩意儿包含的文件不一样,见 #3599 - If Not McVersion.IsFormatFit(File("gameVersion")) Then Continue For + If Not McVersion.IsStandardFormat(File("gameVersion")) Then Continue For Files.Add(New KeyValuePair(Of Integer, List(Of String))(File("fileId"), {File("gameVersion").ToString}.ToList)) Next CurseForgeFileIds = Files.Select(Function(f) f.Key).Distinct.ToList diff --git a/Plain Craft Launcher 2/Modules/Resource/ResourceSearcher.vb b/Plain Craft Launcher 2/Modules/Resource/ResourceSearcher.vb index c7734ca..81d0810 100644 --- a/Plain Craft Launcher 2/Modules/Resource/ResourceSearcher.vb +++ b/Plain Craft Launcher 2/Modules/Resource/ResourceSearcher.vb @@ -99,12 +99,12 @@ Public Class ResourceSearcher Dim Address As String = $"https://api.modrinth.com/v2/search?limit={RESULT_PAGE_SIZE}&index=relevance" If Not String.IsNullOrEmpty(SearchText) Then Address += "&query=" & StringUtils.UrlEscape(SearchText) If Storage.ModrinthOffset > 0 Then Address += "&offset=" & Storage.ModrinthOffset - 'facets=[["categories:'game-mechanics'"],["categories:'forge',categories:'fabric'"],["versions:1.19.3"],["project_type:mod"]] + 'facets=[["categories:'game-mechanics'"],["categories:forge","categories:fabric"],["versions:1.19.3"],["project_type:mod"]] Dim Facets As New List(Of String) Facets.Add($"[""project_type:{Type.ToString.Lower}""]") If Not String.IsNullOrEmpty(Tag) Then Facets.Add($"[""categories:'{Tag.AfterLast("/")}'""]") If ModLoaders <> ModLoaders.None AndAlso Not IgnoreModLoaderFilter Then - Facets.Add($"[""categories:'{ModLoaders.Flags.Select(Function(f) f.ToString.Lower).Join("',categories:'")}'""]") + Facets.Add($"[""categories:{ModLoaders.Flags.Select(Function(f) f.ToString.Lower).Join(""",""categories:")}""]") End If If Not String.IsNullOrEmpty(GameVersion) Then Facets.Add($"[""versions:'{GameVersion}'""]") Address += "&facets=[" & String.Join(",", Facets) & "]" @@ -378,21 +378,19 @@ NextPage: If Request.Type <> ResourceTypes.Any AndAlso Not Project.Types.HasFlagF(Request.Type) Then Continue For If Not String.IsNullOrEmpty(Request.Tag) AndAlso Not JsonEntry("categories").Any(Function(c) c.ToString = Request.Tag.AfterLast("/")) Then Continue For 'Project.Tags 已经转换成中文了,只能从 json 判 + If Request.ModLoaders <> ModLoaders.None AndAlso Not IgnoreModLoaderFilter AndAlso + Not Project.ModLoaders.Flags.Intersect(Request.ModLoaders.Flags).Any() Then Continue For + If Not String.IsNullOrEmpty(Request.GameVersion) AndAlso + Not Project.UnsafeGameVersions.Any(Function(d) d = Request.GameVersion) Then Continue For ProjectList.Add(Project) Next - Dim Facets As New List(Of String) - If Request.ModLoaders <> ModLoaders.None AndAlso Not IgnoreModLoaderFilter Then - Facets.Add($"[""categories:'{Request.ModLoaders.Flags.Select(Function(f) f.ToString.Lower).Join("'"",""categories:'")}'""]") - End If - If Not String.IsNullOrEmpty(Request.GameVersion) Then Facets.Add($"[""versions:'{Request.GameVersion}'""]") - If ProjectList.Any AndAlso Facets.Any Then - Facets.Add("[""project_id:" & ProjectList.Select(Function(p) p.Id).Join(""",""project_id:") & """]") - Dim FilterUrl As String = $"https://api.modrinth.com/v2/search?limit={Math.Min(ProjectList.Count, 100)}&facets=[" & String.Join(",", Facets) & "]" - Logger.Info($"开始 Modrinth 直接获取筛选:{FilterUrl}") - Dim MatchedIds As New List(Of String) - For Each JsonEntry As JObject In DlModRequest(FilterUrl)("hits") - MatchedIds.Add(JsonEntry("project_id").ToString) - Next + If ProjectList.Any AndAlso Request.ModLoaders <> ModLoaders.None AndAlso Not IgnoreModLoaderFilter AndAlso + Not String.IsNullOrEmpty(Request.GameVersion) Then + Dim ProjectIds = ProjectList.Select(Function(p) p.Id).Join(""",""project_id:") + Dim Loaders = Request.ModLoaders.Flags.Select(Function(f) f.ToString.Lower).Join(""",""categories:") + Dim FilterUrl = $"https://api.modrinth.com/v2/search?limit={ProjectList.Count}&facets=" & + $"[[""project_id:{ProjectIds}""],[""categories:{Loaders}""],[""versions:{Request.GameVersion}""]]" + Dim MatchedIds = DlModRequest(FilterUrl)("hits").Select(Function(h) h("project_id").ToString).ToList ProjectList.KeepIf(Function(p) MatchedIds.Contains(p.Id)) End If '更新结果 diff --git a/Plain Craft Launcher 2/Modules/Resource/ResourceVersion.vb b/Plain Craft Launcher 2/Modules/Resource/ResourceVersion.vb index 857c7e4..2272fc3 100644 --- a/Plain Craft Launcher 2/Modules/Resource/ResourceVersion.vb +++ b/Plain Craft Launcher 2/Modules/Resource/ResourceVersion.vb @@ -90,7 +90,7 @@ Public Class ResourceVersion ''' Public DownloadUrls As List(Of String) ''' - ''' 对应文件的 SHA1 或 MD5。 + ''' 对应文件的 SHA1 或 MD5。固定为小写。 ''' Public Hash As String = Nothing ''' @@ -127,8 +127,9 @@ Public Class ResourceVersion .DownloadCount = Data("downloadCount") .FileName = Data("fileName") .Size = Data("fileLength") - .Hash = CType(Data("hashes"), JArray).ToList.FirstOrDefault(Function(s) s("algo").ToObject(Of Integer) = 1)?("value") - If .Hash Is Nothing Then .Hash = CType(Data("hashes"), JArray).ToList.FirstOrDefault(Function(s) s("algo").ToObject(Of Integer) = 2)?("value") + If .Size = 0 Then .Size = -1 'CurseForge 可能错误地返回 0 尺寸(#9104) + .Hash = CType(Data("hashes"), JArray).ToList.FirstOrDefault(Function(s) s("algo").ToObject(Of Integer) = 1)?("value")?.ToString().Lower() + If .Hash Is Nothing Then .Hash = CType(Data("hashes"), JArray).ToList.FirstOrDefault(Function(s) s("algo").ToObject(Of Integer) = 2)?("value")?.ToString().Lower() 'DownloadAddress Dim Url = Data("downloadUrl").ToString If Url = "" Then Url = $"https://edge.forgecdn.net/files/{CInt(.Id.ToString.Substring(0, 4))}/{CInt(.Id.ToString.Substring(4))}/{ .FileName}" @@ -145,13 +146,13 @@ Public Class ResourceVersion 'GameVersions Dim RawVersions As List(Of String) = Data("gameVersions").Select(Function(t) t.ToString.Trim.Lower).ToList .GameVersions = RawVersions. - Where(Function(v) McVersion.IsFormatFit(v)). + Where(Function(v) McVersion.IsStandardFormat(v)). Select(Function(v) v.Replace("-snapshot", " 预览版")). Distinct.ToList If .GameVersions.IsSingle Then .GameVersions = .GameVersions.ToList ElseIf .GameVersions.Count > 1 Then - .GameVersions = .GameVersions.SortByComparison(AddressOf CompareVersionGE).ToList + .GameVersions = .GameVersions.SortByComparison(AddressOf McUtils.CompareVersionGE).ToList If .ResourceType = ResourceTypes.ModPack Then .GameVersions = { .GameVersions(0)}.ToList '整合包理应只 “支持” 一个版本 Else .GameVersions = New List(Of String) From {"未知版本"} @@ -176,7 +177,7 @@ Public Class ResourceVersion .FileName = File("filename") .DownloadUrls = New List(Of String) From {File("url"), DlSourceModGet(File("url"))}.Distinct.ToList '同时添加了镜像源 .Size = File("size") - .Hash = File("hashes")("sha1") + .Hash = File("hashes")("sha1")?.ToString.Lower() End If '类别与 Loaders '结果可能混杂着 Mod、数据包和服务端插件 @@ -208,7 +209,7 @@ Public Class ResourceVersion If .GameVersions.IsSingle Then '无需处理 ElseIf .GameVersions.Count > 1 Then - .GameVersions = .GameVersions.SortByComparison(AddressOf CompareVersionGE).ToList + .GameVersions = .GameVersions.SortByComparison(AddressOf McUtils.CompareVersionGE).ToList If .ResourceType = ResourceTypes.ModPack Then .GameVersions = New List(Of String) From { .GameVersions(0)} '整合包理应只 “支持” 一个版本 ElseIf RawVersions.Any(Function(v) v.RegexCheck("[0-9]{2}w[0-9]{2}[a-z]")) Then .GameVersions = RawVersions.Where(Function(v) v.RegexCheck("[0-9]{2}w[0-9]{2}[a-z]")).ToList @@ -355,7 +356,7 @@ Public Class ResourceVersion .Info = Iterator Function() If ModLoaders <> ModLoaders.None Then Yield ModLoaders.Flags.Join("、"c) - If .Title <> FileName.BeforeLast(".") Then Yield FileName.BeforeLast(".") + If .Title <> PathUtils.GetFileNameWithoutExtension(FileName) Then Yield PathUtils.GetFileNameWithoutExtension(FileName) If Dependencies.Any Then Yield $"{Dependencies.Count} 项前置" If GameVersions.All(Function(v) Not v.Contains(".") OrElse {"w", "snapshot", "rc", "pre", "experimental", "-"}.Any(Function(s) v.ContainsIgnoreCase(s))) Then Yield $"游戏版本 {GameVersions.Join("、"c)}" If DownloadCount > 0 Then Yield $"下载 {If(DownloadCount > 100000, Math.Round(DownloadCount / 10000) & " 万", DownloadCount.ToString() + " ")}次" 'CurseForge 的下载次数经常错误地返回 0 diff --git a/Plain Craft Launcher 2/My Project/AssemblyInfo.vb b/Plain Craft Launcher 2/My Project/AssemblyInfo.vb index c71c896..fb6f794 100644 --- a/Plain Craft Launcher 2/My Project/AssemblyInfo.vb +++ b/Plain Craft Launcher 2/My Project/AssemblyInfo.vb @@ -51,6 +51,6 @@ Imports System.Runtime.InteropServices ' 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号 ' 方法是按如下所示使用“*” - - + + diff --git a/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb b/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb index 852556f..afab214 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb @@ -4,12 +4,10 @@ Public Module ModDownloadLib ''' ''' 获取下载某个 Minecraft 版本的加载器列表。 - ''' 它必须安装到 McFolderSelected,但是可以自定义版本名(不过自定义的版本名不会修改 Json 中的 id 项)。 ''' - Private Function McDownloadClientLoader(Id As String, Optional JsonUrl As String = Nothing, Optional InstanceName As String = Nothing) As List(Of LoaderBase) - InstanceName = If(InstanceName, Id) - Dim VersionFolder As String = McFolderSelected & "versions\" & InstanceName & "\" - + ''' 版本文件输出目录。 + Private Function McDownloadClientLoader(Id As String, JsonUrl As String, VersionFolder As String) As List(Of LoaderBase) + Dim InstanceName = PathUtils.GetLastPart(VersionFolder) Dim Loaders As New List(Of LoaderBase) Dim NewInstance As McInstance = Nothing @@ -54,7 +52,7 @@ Public Module ModDownloadLib '顺手添加 Json 项目 Try Dim InstanceJson As JObject = FileUtils.ReadAsJson(VersionFolder & InstanceName & ".json") - InstanceJson.Add("clientVersion", Id) + InstanceJson("clientVersion") = Id FileUtils.Write($"{VersionFolder}{InstanceName}.json", InstanceJson.ToString) Catch ex As Exception Throw New Exception("添加客户端版本失败", ex) @@ -242,11 +240,11 @@ pause" New NetFile(DlSourceLauncherOrMetaGet(JsonUrl), VersionFolder & Id & ".json", New FileChecker With {.IsJson = True}) }) With {.ProgressWeight = 2}) '获取支持库文件地址 - Loaders.Add(New LoaderTask(Of String, List(Of NetFile))("分析核心 JAR 文件下载地址", + Loaders.Add(New LoaderTask(Of String, List(Of NetFile))("分析版本 JAR 文件下载地址", Sub(Task) Task.Output = New List(Of NetFile) From {DlClientJarGet(New McInstance(VersionFolder), False)} ) With {.ProgressWeight = 0.5, .Show = False}) '下载支持库文件 - Loaders.Add(New LoaderDownload("下载核心 JAR 文件", New List(Of NetFile)) With {.ProgressWeight = 5}) + Loaders.Add(New LoaderDownload("下载版本 JAR 文件", New List(Of NetFile)) With {.ProgressWeight = 5}) '启动 Dim Loader As New LoaderCombo(Of String)("Minecraft " & Id & " 下载", Loaders) With {.OnStateChanged = AddressOf LoaderStateChangedHintOnly} @@ -326,7 +324,6 @@ pause" Private Sub McDownloadOptiFineSave(DownloadInfo As DlOptiFineListEntry) Try - Dim Id As String = DownloadInfo.InstanceName Dim Target As String = Dialogs.SaveFile("选择保存位置", DownloadInfo.FileName, filter:={("jar", "OptiFine Jar")}) If Target Is Nothing Then Return @@ -374,9 +371,9 @@ pause" '添加 Java Wrapper 作为主 Jar Dim Arguments As String If UseJavaWrapper AndAlso Not Settings.Get(Of Boolean)("LaunchAdvanceDisableJLW") Then - Arguments = $"-Doolloo.jlw.tmpdir=""{PathPure.Value.TrimEnd("\")}"" -Duser.home=""{BaseMcFolderHome.TrimEnd("\")}"" -cp ""{Target}"" -jar ""{LaunchUtils.ExtractPatch("Launch\JavaWrapper.jar", PathPure.Value)}"" optifine.Installer" + Arguments = $"-Doolloo.jlw.tmpdir=""{PathUtils.RemoveSlashSuffix(PathPure.Value)}"" -Duser.home=""{PathUtils.RemoveSlashSuffix(BaseMcFolderHome)}"" -cp ""{Target}"" -jar ""{LaunchUtils.ExtractPatch("Launch\JavaWrapper.jar", PathPure.Value)}"" optifine.Installer" Else - Arguments = $"-Duser.home=""{BaseMcFolderHome.TrimEnd("\")}"" -cp ""{Target}"" optifine.Installer" + Arguments = $"-Duser.home=""{PathUtils.RemoveSlashSuffix(BaseMcFolderHome)}"" -cp ""{Target}"" optifine.Installer" End If If Java.Version.Major >= 9 Then Arguments = "--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED " & Arguments '开始启动 @@ -473,13 +470,12 @@ pause" Private Function McDownloadOptiFineLoader(DownloadInfo As DlOptiFineListEntry, McFolder As String, ClientDownloadLoader As LoaderCombo(Of String), ClientFolder As String) As List(Of LoaderBase) '参数初始化 - Dim IsCustomFolder As Boolean = McFolder <> McFolderSelected Dim Id As String = DownloadInfo.InstanceName Dim VersionFolder As String = McFolder & "versions\" & Id & "\" Dim IsNewerVersion As Boolean = DownloadInfo.Inherit.Contains("w") OrElse McVersion.VersionToDrop(DownloadInfo.Inherit) >= 140 Dim Target As String = If(IsNewerVersion, - $"{RequestTaskTempFolder()}OptiFine.jar", - $"{McFolder}libraries\optifine\OptiFine\{DownloadInfo.FileName.Replace("OptiFine_", "").Replace(".jar", "").Replace("preview_", "")}\{DownloadInfo.FileName.Replace("OptiFine_", "OptiFine-").Replace("preview_", "")}") + $"{RequestTaskTempFolder(True)}OptiFine.jar", + $"{McFolder}libraries\optifine\OptiFine\{PathUtils.GetFileNameWithoutExtension(DownloadInfo.FileName).Replace("OptiFine_", "").Replace("preview_", "")}\{DownloadInfo.FileName.Replace("OptiFine_", "OptiFine-").Replace("preview_", "")}") Dim Loaders As New List(Of LoaderBase) '获取下载地址 @@ -522,7 +518,6 @@ pause" Loop If Task.IsCanceled Then Return '复制原版文件 - If Not IsCustomFolder Then Return SyncLock VanillaSyncLock Dim ClientName As String = PathUtils.GetLastPart(ClientFolder) DirectoryUtils.Create(McFolder & "versions\" & DownloadInfo.Inherit) @@ -540,7 +535,7 @@ pause" Logger.Info($"检测为新版 OptiFine:{DownloadInfo.Inherit}") Loaders.Add(New LoaderTask(Of List(Of NetFile), Boolean)("安装 OptiFine(方式 A)", Sub(Task As LoaderTask(Of List(Of NetFile), Boolean)) - Dim BaseMcFolderHome As String = RequestTaskTempFolder() + Dim BaseMcFolderHome As String = RequestTaskTempFolder(True) Dim BaseMcFolder As String = BaseMcFolderHome & ".minecraft\" Try '准备安装环境 @@ -597,7 +592,7 @@ Retry: ""releaseTime"": """ & If(DownloadInfo.ReleaseTime = "", InheritInstance.ReleaseTime.ToString("yyyy'-'MM'-'dd"), DownloadInfo.ReleaseTime.Replace("/", "-")) & "T23:33:33+08:00"", ""type"": ""release"", ""libraries"": [ - {""name"": ""optifine:OptiFine:" & DownloadInfo.FileName.Replace("OptiFine_", "").Replace(".jar", "").Replace("preview_", "") & """}, + {""name"": ""optifine:OptiFine:" & PathUtils.GetFileNameWithoutExtension(DownloadInfo.FileName).Replace("OptiFine_", "").Replace("preview_", "") & """}, {""name"": ""net.minecraft:launchwrapper:1.12""} ], ""mainClass"": ""net.minecraft.launchwrapper.Launch""," @@ -785,19 +780,14 @@ Retry: ''' 这不会补全所需的支持库文件,需要后续手动补全支持库。 ''' Private Function McDownloadLiteLoaderLoader(DownloadInfo As DlLiteLoaderListEntry, McFolder As String) As List(Of LoaderBase) - - '参数初始化 - Dim IsCustomFolder As Boolean = McFolder <> McFolderSelected - Dim Id As String = DownloadInfo.Inherit - Dim Target As String = PathTemp & "Download\" & Id & "-Liteloader.jar" - Dim VersionName As String = DownloadInfo.Inherit & "-LiteLoader" - Dim VersionFolder As String = McFolder & "versions\" & VersionName & "\" Dim Loaders As New List(Of LoaderBase) '安装 Loaders.Add(New LoaderTask(Of String, String)("安装 LiteLoader", Sub(Task As LoaderTask(Of String, String)) Try + Dim VersionName As String = DownloadInfo.Inherit & "-LiteLoader" + Dim VersionFolder As String = McFolder & "versions\" & VersionName & "\" '构造版本 Json Dim VersionJson As New JObject VersionJson.Add("id", VersionName) @@ -945,7 +935,7 @@ Retry: '添加 Java Wrapper 作为主 Jar Dim Arguments As String If UseJavaWrapper AndAlso Not Settings.Get(Of Boolean)("LaunchAdvanceDisableJLW") Then - Arguments = $"-Doolloo.jlw.tmpdir=""{PathPure.Value.TrimEnd("\")}"" -cp ""{PathTemp}Cache\forge_installer.jar;{Target}"" -jar ""{LaunchUtils.ExtractPatch("Launch\JavaWrapper.jar", PathPure.Value)}"" com.bangbang93.ForgeInstaller ""{McFolder}" + Arguments = $"-Doolloo.jlw.tmpdir=""{PathUtils.RemoveSlashSuffix(PathPure.Value)}"" -cp ""{PathTemp}Cache\forge_installer.jar;{Target}"" -jar ""{LaunchUtils.ExtractPatch("Launch\JavaWrapper.jar", PathPure.Value)}"" com.bangbang93.ForgeInstaller ""{McFolder}" Else Arguments = $"-cp ""{PathTemp}Cache\forge_installer.jar;{Target}"" com.bangbang93.ForgeInstaller ""{McFolder}" End If @@ -1078,9 +1068,7 @@ Retry: ''' 获取下载某个 Forgelike 版本的加载器列表。 ''' Private Function McDownloadForgelikeLoader(IsNeoForge As Boolean, LoaderVersion As String, NewInstanceName As String, Inherit As String, Info As DlForgelikeEntry, McFolder As String, ClientDownloadLoader As LoaderCombo(Of String), ClientFolder As String) As List(Of LoaderBase) - '参数初始化 - McFolder = If(McFolder, McFolderSelected) If IsNeoForge AndAlso Info Is Nothing Then '需要传入 API Name,但整合包版本可能不以 1.20.1- 开头,所以需要进行特别处理 If Inherit = "1.20.1" AndAlso Not LoaderVersion.StartsWithF("1.20.1-") Then @@ -1095,12 +1083,9 @@ Retry: LoaderVersion = LoaderVersion.AfterLast("-") End If Dim LoaderName As String = If(IsNeoForge, "NeoForge", "Forge") - Dim IsCustomFolder As Boolean = McFolder <> McFolderSelected - Dim InstallerAddress As String = RequestTaskTempFolder() & "forge_installer.jar" + Dim InstallerAddress As String = RequestTaskTempFolder(True) & "forge_installer.jar" Dim VersionFolder As String = $"{McFolder}versions\{NewInstanceName}\" - Dim DisplayName As String = $"{LoaderName} {Inherit} - {LoaderVersion}" Dim Loaders As New List(Of LoaderBase) - Dim LibVersionFolder As String = $"{McFolderSelected}versions\{NewInstanceName}\" '作为 Lib 文件目标的版本文件夹 '获取 Forge 下载信息 If Info Is Nothing Then @@ -1112,7 +1097,7 @@ Retry: Task.Progress = 0.8 '查找对应版本 For Each ForgeVersion In ForgeLoader.Output - If CompareVersion(ForgeVersion.Version.ToString, LoaderVersion) = 0 Then + If McUtils.CompareVersion(ForgeVersion.Version.ToString, LoaderVersion) = 0 Then Info = ForgeVersion Return End If @@ -1147,6 +1132,7 @@ Retry: Loaders.Add(New LoaderDownload($"下载 {LoaderName} 主文件", New List(Of NetFile)) With {.ProgressWeight = 9}) '安装(仅在新版安装时需要原版 Jar) + Dim CurrentMcFolder = McFolderSelected If IsNeoForge OrElse LoaderVersion.BeforeFirst(".") >= 20 Then Logger.Info($"检测为{If(IsNeoForge, " Neo", "新版 ")}Forge:{LoaderVersion}") Dim Libs As List(Of McLibEntry) = Nothing @@ -1162,7 +1148,7 @@ Retry: Dim Json2 As JObject = Installer.GetEntry("version.json").Open().ReadString().DeserializeJson() Json.Merge(Json2) '获取 Lib 下载信息 - Libs = McLibListGetWithJson(Json, True) + Libs = McLibListGetWithJson(Json, True, CurrentMcFolder) '添加 Mappings 下载信息 If Json("data") IsNot Nothing AndAlso Json("data")("MOJMAPS") IsNot Nothing Then '下载原版 Json 文件 @@ -1170,7 +1156,7 @@ Retry: Dim RawJson As JObject = NetRequestByLoader(DlSourceLauncherOrMetaGet(DlClientListGet(Inherit)), IsJson:=True).DeserializeJson() '[net.minecraft:client:1.17.1-20210706.113038:mappings@txt] 或 @tsrg] Dim OriginalName As String = Json("data")("MOJMAPS")("client").ToString.Trim("[]".ToCharArray()).BeforeFirst("@") - Dim Address = McLibGet(OriginalName).Replace(".jar", "." & Json("data")("MOJMAPS")("client").ToString.Trim("[]".ToCharArray()).Split("@")(1)) + Dim Address = Path.ChangeExtension(McLibGet(OriginalName, CustomMcFolder:=CurrentMcFolder), "." & Json("data")("MOJMAPS")("client").ToString.Trim("[]".ToCharArray()).Split("@")(1)) Dim ClientMappings As JToken = RawJson("downloads")("client_mappings") Libs.Add(New McLibEntry With { .IsNatives = False, .LocalPath = Address, .OriginalName = OriginalName, @@ -1187,7 +1173,7 @@ Retry: Exit For End If Next - Task.Output = McLibNetFilesFromTokens(Libs, McFolderSelected) + Task.Output = McLibNetFilesFromTokens(Libs, CurrentMcFolder) Catch ex As Exception Throw New Exception($"获取{If(IsNeoForge, " Neo", "新版 ")}Forge 支持库列表失败", ex) Finally @@ -1198,13 +1184,11 @@ Retry: Loaders.Add(New LoaderTask(Of List(Of NetFile), Boolean)($"获取 {LoaderName} 支持库文件", Sub(Task As LoaderTask(Of List(Of NetFile), Boolean)) #Region "Forgelike 文件" - If IsCustomFolder Then - For Each LibFile As McLibEntry In Libs - Dim RealPath As String = LibFile.LocalPath.Replace(McFolderSelected, McFolder) - If Not FileUtils.Exists(RealPath) Then FileUtils.Copy(LibFile.LocalPath, RealPath) - Logger.Trace($"复制的 {LoaderName} 支持库文件:{LibFile.LocalPath}") - Next - End If + For Each LibFile As McLibEntry In Libs + Dim RealPath As String = Path.Combine(McFolder, LibFile.LocalPath.Substring(CurrentMcFolder.Length)) + If Not FileUtils.Exists(RealPath) Then FileUtils.Copy(LibFile.LocalPath, RealPath) + Logger.Trace($"复制的 {LoaderName} 支持库文件:{LibFile.LocalPath}") + Next #End Region #Region "原版文件" '等待原版文件下载完成 @@ -1218,7 +1202,6 @@ Retry: Loop If Task.IsCanceled Then Return '复制原版文件 - If Not IsCustomFolder Then Return SyncLock VanillaSyncLock Dim ClientName As String = PathUtils.GetLastPart(ClientFolder) DirectoryUtils.Create(McFolder & "versions\" & Inherit) @@ -1233,14 +1216,10 @@ Retry: End Sub) With {.ProgressWeight = 0.1, .Show = False}) Loaders.Add(New LoaderTask(Of Boolean, Boolean)(If(IsNeoForge, "安装 NeoForge", "安装 Forge(方式 A)"), Sub(Task As LoaderTask(Of Boolean, Boolean)) - Dim Installer As ZipArchive = Nothing Try Logger.Info($"开始进行 Forgelike 安装:{InstallerAddress}") '记录当前文件夹列表(在新建目标文件夹之前) Dim OldList = DirectoryUtils.EnumerateDirectories(McFolder & "versions\").ToList() - '解压并获取信息 - Installer = FileUtils.OpenZip(InstallerAddress) - Dim Json As JObject = Installer.GetEntry("install_profile.json").Open().ReadString().DeserializeJson() '新建目标版本文件夹 DirectoryUtils.Create(VersionFolder) Task.Progress = 0.04 @@ -1292,7 +1271,6 @@ Retry: Finally '清理文件 Try - Installer?.Dispose() FileUtils.Delete(InstallerAddress) Catch ex As Exception Logger.Warn(ex, $"安装 {LoaderName} 清理文件时出错") @@ -1487,19 +1465,19 @@ Retry: ''' ''' 尝试获取某个 MC 版本对应的 Forge 推荐版本。如果不可用会返回 Nothing。 ''' - Public Function McDownloadForgeRecommendedGet(McVersion As String) As String + Public Function McDownloadForgeRecommendedGet(McVersionName As String) As String Try - If McVersion Is Nothing Then Return Nothing + If McVersionName Is Nothing Then Return Nothing Dim List As String = FileUtils.TryReadAsString(PathTemp & "Cache\ForgeRecommendedList.json") If String.IsNullOrEmpty(List) Then Logger.Info("没有 Forge 推荐版本缓存文件") Return Nothing End If Dim Json As JObject = List.DeserializeJson() - If Json Is Nothing OrElse (Not If(McVersion, "").Contains(".")) OrElse Not Json.ContainsKey(McVersion) Then Return Nothing - Return If(Json(McVersion), "").ToString + If Json Is Nothing OrElse (Not If(McVersionName, "").Contains(".")) OrElse Not Json.ContainsKey(McVersionName) Then Return Nothing + Return If(Json(McVersionName), "").ToString Catch ex As Exception - Logger.Error(ex, $"获取 Forge 推荐版本失败({If(McVersion, "null")})") + Logger.Error(ex, $"获取 Forge 推荐版本失败({If(McVersionName, "null")})") Return Nothing End Try End Function @@ -1640,7 +1618,6 @@ Retry: Private Function McDownloadFabricLoader(FabricVersion As String, MinecraftName As String, McFolder As String) As List(Of LoaderBase) '参数初始化 - Dim IsCustomFolder As Boolean = McFolder <> McFolderSelected Dim Id As String = "fabric-loader-" & FabricVersion & "-" & MinecraftName Dim VersionFolder As String = McFolder & "versions\" & Id & "\" Dim Loaders As New List(Of LoaderBase) @@ -1702,83 +1679,437 @@ Retry: #End Region -#Region "合并安装" +#Region "安装" ''' ''' 安装请求。 ''' Public Class McInstallRequest - ''' - ''' 必填。安装目标版本名称。 + ''' 新版本安装必填。安装目标版本名称。 ''' Public NewInstanceName As String ''' ''' 必填。安装目标文件夹。 ''' - Public VersionFolder As String - + Public InstanceFolder As String ''' - ''' 必填。欲下载的 Minecraft 的版本名。 + ''' 必填。欲安装的 Minecraft 与组件版本。 ''' - Public MinecraftName As String = Nothing + Public Components As McComponents ''' ''' 可选。欲下载的 Minecraft Json 地址。 ''' Public MinecraftJson As String = Nothing - - '若要下载 OptiFine,则需要在下面两项中完成至少一项 - ''' - ''' 欲下载的 OptiFine 版本名。例如 HD_U_F6_pre1。 - ''' - Public OptiFineVersion As String = Nothing - ''' - ''' 欲下载的 OptiFine 详细信息。 - ''' - Public OptiFineEntry As DlOptiFineListEntry = Nothing - - '若要下载 Forge,则需要在下面两项中完成至少一项 - ''' - ''' 欲下载的 Forge 版本名。接受例如 36.1.4 / 14.23.5.2859 / 1.19-41.1.0 的输入。 - ''' - Public ForgeVersion As String = Nothing ''' ''' 欲下载的 Forge。 ''' Public ForgeEntry As DlForgeVersionEntry = Nothing - - '若要下载 NeoForge,则需要在下面两项中完成至少一项 - ''' - ''' 欲下载的 NeoForge 版本名。 - ''' - Public NeoForgeVersion As String = Nothing ''' ''' 欲下载的 NeoForge。 ''' Public NeoForgeEntry As DlNeoForgeListEntry = Nothing - - ''' - ''' 欲下载的 Fabric Loader 版本名。 - ''' - Public FabricVersion As String = Nothing - ''' ''' 欲下载的 Fabric API 信息。 ''' Public FabricApi As ResourceVersion = Nothing - ''' ''' 欲下载的 OptiFabric 信息。 ''' Public OptiFabric As ResourceVersion = Nothing - ''' ''' 欲下载的 LiteLoader 详细信息。 ''' Public LiteLoaderEntry As DlLiteLoaderListEntry = Nothing - + ''' + ''' 是否为中间产物。 + ''' + Public IsIntermediateOutput As Boolean = False End Class + ''' + ''' 获取合并安装加载器列表。 + ''' + Public Function McInstallLoadersGet(Request As McInstallRequest) As List(Of LoaderBase) + '获取缓存目录(安装 Mod 加载器的文件夹不能包含空格) + Dim TempMcFolder As String = RequestTaskTempFolder(Request.Components.OptiFineEntry IsNot Nothing OrElse + Request.Components.ModLoaderVersions.ContainsKey(ModLoaders.Forge) OrElse Request.Components.ModLoaderVersions.ContainsKey(ModLoaders.NeoForge)) + + '获取各个 Mod 加载器的缓存文件夹 + Dim OptiFineEntry = Request.Components.OptiFineEntry?.ToObject(Of DlOptiFineListEntry)() + Dim OptiFineFolder = If(OptiFineEntry Is Nothing, Nothing, $"{TempMcFolder}versions\{OptiFineEntry.InstanceName}") + Dim ForgeVersion = If(Request.Components.ModLoaderVersions.ContainsKey(ModLoaders.Forge), Request.Components.ModLoaderVersions(ModLoaders.Forge), Nothing) + Dim ForgeFolder = If(ForgeVersion Is Nothing, Nothing, $"{TempMcFolder}versions\forge-{ForgeVersion}") + Dim NeoForgeVersion = If(Request.Components.ModLoaderVersions.ContainsKey(ModLoaders.NeoForge), Request.Components.ModLoaderVersions(ModLoaders.NeoForge), Nothing) + Dim NeoForgeFolder = If(NeoForgeVersion Is Nothing, Nothing, $"{TempMcFolder}versions\neoforge-{NeoForgeVersion}") + Dim LiteLoaderVersion = If(Request.Components.ModLoaderVersions.ContainsKey(ModLoaders.LiteLoader), Request.Components.ModLoaderVersions(ModLoaders.LiteLoader), Nothing) + Dim LiteLoaderFolder = If(LiteLoaderVersion Is Nothing, Nothing, $"{TempMcFolder}versions\{Request.Components.MinecraftVersion}-LiteLoader") + Dim FabricVersion = If(Request.Components.ModLoaderVersions.ContainsKey(ModLoaders.Fabric), Request.Components.ModLoaderVersions(ModLoaders.Fabric), Nothing) + Dim FabricFolder = If(FabricVersion Is Nothing, Nothing, $"{TempMcFolder}versions\fabric-loader-{FabricVersion}-{Request.Components.MinecraftVersion}") + + '判断 OptiFine 是否作为 Mod 进行下载 + Dim Modable = Request.Components.ModLoaderVersions.Any + Dim ModsTempFolder As String = $"{TempMcFolder}mods\" + Dim OptiFineAsMod = OptiFineEntry IsNot Nothing AndAlso Modable '选择了 OptiFine 与任意 Mod 加载器 + If OptiFineAsMod Then + Logger.Info("OptiFine 将作为 Mod 进行下载") + If LiteLoaderVersion IsNot Nothing Then + OptiFineFolder = $"{ModsTempFolder}{Request.Components.MinecraftVersion}\" '#8147 + Else + OptiFineFolder = ModsTempFolder + End If + End If + + '记录日志 + If OptiFineFolder IsNot Nothing Then Logger.Info($"OptiFine 缓存:{OptiFineFolder}") + If ForgeFolder IsNot Nothing Then Logger.Info($"Forge 缓存:{ForgeFolder}") + If NeoForgeFolder IsNot Nothing Then Logger.Info($"NeoForge 缓存:{NeoForgeFolder}") + If FabricFolder IsNot Nothing Then Logger.Info($"Fabric 缓存:{FabricFolder}") + If LiteLoaderFolder IsNot Nothing Then Logger.Info($"LiteLoader 缓存:{LiteLoaderFolder}") + Logger.Info($"对应的原版版本:{Request.Components.MinecraftVersion}") + + '重复版本检查 + Dim VersionFolder As String = PathUtils.AddSlashSuffix(Request.InstanceFolder) + If FileUtils.Exists($"{VersionFolder}{Request.NewInstanceName}.json") Then + Hint("版本 " & Request.NewInstanceName & " 已经存在!", HintType.Red) + Throw New OperationCanceledException + End If + + Dim LoaderList As New List(Of LoaderBase) + Dim ManagedFileSources As New Dictionary(Of String, String) + '添加忽略标识 + LoaderList.Add(New LoaderTask(Of Integer, Integer)("添加忽略标识", Sub() FileUtils.Write(VersionFolder & ".pclignore", "用于临时地在 PCL 的版本列表中屏蔽此版本。")) With {.Show = False}) + 'Fabric API + If Request.Components.FabricApiVersion IsNot Nothing Then + Dim DownloadFile = Request.FabricApi.ToNetFile(ModsTempFolder, ResourceVersion.DownloadReason.Dependency, Request.Components.MinecraftVersion, ModLoaders.Fabric) + ManagedFileSources.Add("FabricApi", DownloadFile.LocalPath) + LoaderList.Add(New LoaderDownload("下载 Fabric API", New List(Of NetFile) From {DownloadFile}) With { + .ProgressWeight = 3, .Show = True, .Block = False}) + End If + 'OptiFabric + If Request.Components.OptiFabricVersion IsNot Nothing Then + Dim DownloadFile = Request.OptiFabric.ToNetFile(ModsTempFolder, ResourceVersion.DownloadReason.Dependency, Request.Components.MinecraftVersion, ModLoaders.Fabric) + ManagedFileSources.Add("OptiFabric", DownloadFile.LocalPath) + LoaderList.Add(New LoaderDownload("下载 OptiFabric", New List(Of NetFile) From {DownloadFile}) With { + .ProgressWeight = 3, .Show = True, .Block = False}) + End If + '原版 + Dim ClientLoader = New LoaderCombo(Of String)("下载原版 " & Request.Components.MinecraftVersion, + McDownloadClientLoader(Request.Components.MinecraftVersion, Request.MinecraftJson, VersionFolder)) With { + .ProgressWeight = 39, .Show = False, .Block = Not Modable AndAlso OptiFineEntry Is Nothing} + LoaderList.Add(ClientLoader) + 'OptiFine + If OptiFineEntry IsNot Nothing Then + If OptiFineAsMod Then ManagedFileSources.Add("OptiFine", OptiFineFolder & OptiFineEntry.FileName) + LoaderList.Add(New LoaderCombo(Of String)("下载 OptiFine " & OptiFineEntry.DisplayName, + If(OptiFineAsMod, + McDownloadOptiFineSaveLoader(OptiFineEntry, OptiFineFolder & OptiFineEntry.FileName), + McDownloadOptiFineLoader(OptiFineEntry, TempMcFolder, ClientLoader, VersionFolder))) With { + .ProgressWeight = If(Modable, 16, 24), .Show = False, .Block = Not Modable}) + End If + 'Forge + If ForgeVersion IsNot Nothing Then + LoaderList.Add(New LoaderCombo(Of String)("下载 Forge " & ForgeVersion, + McDownloadForgelikeLoader(False, ForgeVersion, "forge-" & ForgeVersion, Request.Components.MinecraftVersion, Request.ForgeEntry, TempMcFolder, ClientLoader, VersionFolder)) With { + .ProgressWeight = 25, .Show = False, .Block = FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing AndAlso Request.NeoForgeEntry Is Nothing}) + End If + 'NeoForge + If NeoForgeVersion IsNot Nothing Then + LoaderList.Add(New LoaderCombo(Of String)("下载 NeoForge " & NeoForgeVersion, + McDownloadForgelikeLoader(True, NeoForgeVersion, "neoforge-" & NeoForgeVersion, Request.Components.MinecraftVersion, Request.NeoForgeEntry, TempMcFolder, ClientLoader, VersionFolder)) With { + .ProgressWeight = 25, .Show = False, .Block = Request.ForgeEntry Is Nothing AndAlso FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing}) + End If + 'LiteLoader + If LiteLoaderVersion IsNot Nothing Then + LoaderList.Add(New LoaderCombo(Of String)("下载 LiteLoader " & Request.Components.MinecraftVersion, + McDownloadLiteLoaderLoader(Request.LiteLoaderEntry, TempMcFolder)) With { + .ProgressWeight = 1, .Show = False, .Block = FabricVersion Is Nothing}) + End If + 'Fabric + If FabricVersion IsNot Nothing Then + LoaderList.Add(New LoaderCombo(Of String)("下载 Fabric " & FabricVersion, + McDownloadFabricLoader(FabricVersion, Request.Components.MinecraftVersion, TempMcFolder)) With { + .ProgressWeight = 2, .Show = False, .Block = True}) + End If + '合并安装 + Dim MinecraftFolder = McFolderSelected + Dim TargetFolder As String = Nothing + LoaderList.Add(New LoaderTask(Of String, String)("安装游戏", + Sub(Task As LoaderTask(Of String, String)) + '合并 JSON:将多个版本 JSON 进行合并,如果已存在则直接覆盖 + MergeJson(VersionFolder, OptiFineFolder, OptiFineAsMod, ForgeFolder, NeoForgeFolder, FabricFolder, LiteLoaderFolder) + Task.Progress = 0.2 + '迁移文件 + DirectoryUtils.Copy(TempMcFolder & "libraries", MinecraftFolder & "libraries") + Task.Progress = 0.8 + '创建 Mod 和资源包文件夹 + TargetFolder = If(Request.IsIntermediateOutput, VersionFolder, New McInstance(VersionFolder).PathIndie) '版本隔离信息在此时被决定 + Dim ModsFolder = TargetFolder & "mods\" + If DirectoryUtils.Exists(ModsTempFolder) Then + DirectoryUtils.Copy(ModsTempFolder, ModsFolder) + ElseIf Modable Then + DirectoryUtils.Create(ModsFolder) + Logger.Info($"自动创建 Mod 文件夹:{ModsFolder}") + End If + Dim ResourcepacksFolder = TargetFolder & "resourcepacks\" + DirectoryUtils.Create(ResourcepacksFolder) + Logger.Info($"自动创建资源包文件夹:{ResourcepacksFolder}") + End Sub) With {.ProgressWeight = 2, .Block = True}) + '补全文件 + Dim LoadersLib As New List(Of LoaderBase) + LoadersLib.Add(New LoaderTask(Of String, List(Of NetFile))("分析游戏支持库文件(副加载器)", Sub(Task) Task.Output = McLibNetFilesFromInstance(New McInstance(VersionFolder))) With {.ProgressWeight = 1, .Show = False}) + LoadersLib.Add(New LoaderDownload("下载游戏支持库文件(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 7, .Show = False}) + LoaderList.Add(New LoaderCombo(Of String)("下载游戏支持库文件", LoadersLib) With {.ProgressWeight = 8}) + '记录安装清单 + LoaderList.Add(New LoaderTask(Of Integer, Integer)("记录安装清单", + Sub() + Dim ManagedFiles As New List(Of McInstallManifest.ManagedFile) + '记录由 PCL 管理的 Mod 信息 + For Each Entry In ManagedFileSources + Dim RelativePath = Entry.Value.Substring(ModsTempFolder.Length) + Dim FullPath = PathUtils.ForCompare(Path.Combine(TargetFolder, "mods", RelativePath)) + ManagedFiles.Add(New McInstallManifest.ManagedFile("mods/" & RelativePath.Replace("\", "/").ToLowerInvariant(), + CryptographyUtils.ComputeFileHash(FullPath, CryptographyUtils.HashMethod.Sha1), FileUtils.GetInfo(FullPath).LastWriteTimeUtc, Entry.Key)) + Next + '处理加载器版本信息 + Dim TempLoaderVersions As New Dictionary(Of ModLoaders, String)(Request.Components.ModLoaderVersions) + If ForgeVersion IsNot Nothing AndAlso ForgeVersion.StartsWithF("1.") AndAlso ForgeVersion.Contains("-") Then + TempLoaderVersions(ModLoaders.Forge) = ForgeVersion.AfterLast("-") + End If + If NeoForgeVersion IsNot Nothing AndAlso NeoForgeVersion.StartsWithF("1.20.1-") Then + TempLoaderVersions(ModLoaders.NeoForge) = NeoForgeVersion.Replace("1.20.1-", "") + End If + '存储 Manifest + Dim Target = New McComponents(Request.Components.MinecraftVersion, TempLoaderVersions, + Request.Components.OptiFineEntry, Request.Components.FabricApiVersion, Request.Components.OptiFabricVersion) + Dim InstanceConfig = New McInstance(VersionFolder).Config + Configs.McInstallManifest.Set(New McInstallManifest(Versions.McInstallManifestVersion, Target, Date.UtcNow, ManagedFiles), InstanceConfig) + InstanceConfig.Save() + End Sub) With {.Show = False}) + '删除忽略标识 + If Not Request.IsIntermediateOutput Then + LoaderList.Add(New LoaderTask(Of Integer, Integer)("删除忽略标识", Sub() FileUtils.Delete(VersionFolder & ".pclignore")) With {.Show = False}) + End If + '总加载器 + Return LoaderList + End Function + Private Sub MergeJson(VersionFolder As String, OptiFineFolder As String, OptiFineAsMod As Boolean, + ForgeFolder As String, NeoForgeFolder As String, FabricFolder As String, LiteLoaderFolder As String) + Dim ProfileSources As New List(Of (Name As String, Folder As String)) From {("Minecraft", VersionFolder)} + If OptiFineFolder IsNot Nothing AndAlso Not OptiFineAsMod Then ProfileSources.Add(("OptiFine", OptiFineFolder)) + If ForgeFolder IsNot Nothing Then ProfileSources.Add(("Forge", ForgeFolder)) + If NeoForgeFolder IsNot Nothing Then ProfileSources.Add(("NeoForge", NeoForgeFolder)) + If LiteLoaderFolder IsNot Nothing Then ProfileSources.Add(("LiteLoader", LiteLoaderFolder)) + If FabricFolder IsNot Nothing Then ProfileSources.Add(("Fabric", FabricFolder)) + Logger.Info($"开始进行版本合并,输出:{VersionFolder}," & String.Join(",", ProfileSources.Select(Function(Source) $"{Source.Name}:{Source.Folder}"))) + '读取文件并检查文件是否合规 + Dim LoadedProfiles As New List(Of (Name As String, Json As JObject)) + For Each Source In ProfileSources + Dim ProfilePath = Path.Combine(Source.Folder, PathUtils.GetLastPart(Source.Folder) & ".json") + Dim ProfileText = If(FileUtils.TryReadAsString(ProfilePath), "") + If Not ProfileText.StartsWithF("{") Then Throw New Exception($"{Source.Name} json 有误,地址:{ProfilePath},前段内容:{ProfileText.Substring(0, Math.Min(ProfileText.Length, 1000))}") + LoadedProfiles.Add((Source.Name, ProfileText.DeserializeJson(Of JObject)())) + Next + '先获取 minecraftArguments,避免合并时原版参数被覆盖 + Dim AllArguments = LoadedProfiles.Select(Function(Profile) If(Profile.Json("minecraftArguments")?.ToString(), " ")).Join(" ") + '合并(相关讨论见 #2801) + Dim MergedJson = LoadedProfiles(0).Json 'Minecraft 原版 JSON + For Each Loaded In LoadedProfiles.Skip(1) + Loaded.Json.Remove("releaseTime") + Loaded.Json.Remove("time") + MergedJson.Merge(Loaded.Json) + Next + '填入 minecraftArguments + Dim SplitArguments As New List(Of String) + For Each Argument In AllArguments.Split(" ", True).Select(Function(Value) Value.Trim()) + If Not Argument.StartsWithF("-") AndAlso SplitArguments.Any Then + Dim LastArgument = SplitArguments.Last + If LastArgument.StartsWithF("-") AndAlso Not LastArgument.Contains(" ") Then + SplitArguments(SplitArguments.Count - 1) = LastArgument & " " & Argument + Continue For + End If + End If + SplitArguments.Add(Argument) + Next + If SplitArguments.Any() Then MergedJson("minecraftArguments") = SplitArguments.Distinct().Join(" ") + '修改 + MergedJson.Remove("_comment_") + MergedJson.Remove("inheritsFrom") + MergedJson.Remove("jar") + Dim VersionName = PathUtils.GetLastPart(VersionFolder) + MergedJson("id") = VersionName + '保存 + FileUtils.Write(Path.Combine(VersionFolder, VersionName & ".json"), MergedJson.ToString()) + Logger.Info($"版本合并 {VersionName} 完成") + End Sub + ''' + ''' 进行合并安装。返回是否已经开始安装。 + ''' + Public Function McInstallStart(Request As McInstallRequest) As Boolean + Try + Dim Loader As New LoaderCombo(Of String)(Request.NewInstanceName & " 安装", McInstallLoadersGet(Request)) With {.OnStateChanged = AddressOf McInstallState} + '启动 + Loader.Start(Request.InstanceFolder) + LoaderTaskbarAdd(Loader) + FrmMain.BtnExtraDownload.ShowRefresh() + FrmMain.BtnExtraDownload.Ribble() + Return True + Catch ex As Exception + Logger.Error(ex, "开始合并安装失败") + Return False + End Try + End Function + ''' + ''' 进行修改安装。返回是否已经开始安装。 + ''' + Public Function McModificationStart(Request As McInstallRequest, Instance As McInstance) As Boolean + Try + '检查版本目录下的 Natives,防止修改正在运行的游戏 + Dim TargetFolder = Instance.PathIndie + Dim SubLoaders As New List(Of LoaderBase) + SubLoaders.Add(New LoaderTask(Of Integer, Integer)("检查 Natives 文件占用", + Sub() + For Each File In DirectoryUtils.EnumerateFiles(GetNativesFolder(Instance), includeSubDirectories:=True, searchPattern:="*.dll") + If Not FileUtils.IsAvailable(File) Then Throw New Exception($"Natives 文件正在被占用,请关闭游戏后再次尝试:{File}") + Next + End Sub) With {.Show = False}) + '安装修改后的 MC 版本 + Dim BuildFolder = RequestTaskTempFolder() & Instance.Name & "\" + Dim BuildRequest As New McInstallRequest With { + .NewInstanceName = Instance.Name, + .InstanceFolder = BuildFolder, + .Components = Request.Components, + .MinecraftJson = Request.MinecraftJson, + .ForgeEntry = Request.ForgeEntry, + .NeoForgeEntry = Request.NeoForgeEntry, + .FabricApi = Request.FabricApi, + .OptiFabric = Request.OptiFabric, + .LiteLoaderEntry = Request.LiteLoaderEntry, + .IsIntermediateOutput = True + } + SubLoaders.AddRange(McInstallLoadersGet(BuildRequest)) + '版本合并 + Dim RenameFailed As Boolean = False + SubLoaders.Add(New LoaderTask(Of Integer, Integer)("修改版本", + Sub(Task) + 'TODO: 提交前显示文件修改列表(大量文件按“XXX 文件夹下 XXX 项”汇总),可以询问玩家是否覆盖原版本或作为新版本安装(不覆盖)? + McInstallCommit(Instance, Configs.McInstallManifest.Get(New McInstance(BuildFolder).Config), BuildFolder, TargetFolder) + '重命名和引用交接 + Dim NewInstance As McInstance = Nothing + If Request.NewInstanceName Is Nothing Then + NewInstance = New McInstance(Instance.PathVersion).Load() + Else + Try + NewInstance = Instance.Rename(Request.NewInstanceName) + Catch ex As Exception + RenameFailed = True + Logger.Warn(ex, $"版本修改成功,但重命名为 {Request.NewInstanceName} 失败:{ex.GetDisplay(False)}", LogBehavior.None) + NewInstance = New McInstance(Instance.PathVersion).Load() + End Try + End If + If NewInstance Is Nothing Then NewInstance = New McInstance(Instance.PathVersion).Load() + RunInUiWait( + Sub() + WriteIni(McFolderSelected & "PCL.ini", "InstanceCache", "") + '未切换则选中修改后的版本 + If PageInstanceLeft.Instance IsNot Nothing AndAlso + PathUtils.IsSame(PageInstanceLeft.Instance.PathVersion, Instance.PathVersion) Then + PageInstanceLeft.Instance = NewInstance + End If + If McInstanceSelected IsNot Nothing AndAlso + PathUtils.IsSame(McInstanceSelected.PathVersion, Instance.PathVersion) Then + McInstanceSelected = NewInstance + End If + End Sub) + End Sub) With {.ProgressWeight = 1, .Block = True}) + '实际修改任务 + Dim Loader As New LoaderCombo(Of String)(Instance.Name & " 修改", SubLoaders) With {.OnStateChanged = + Sub(CurrentLoader) + '流程收尾 + Select Case CurrentLoader.State + Case LoadState.Finished + If Not RenameFailed Then + Hint(CurrentLoader.Name & "成功!", HintType.Green) + Else + Hint(CurrentLoader.Name & "成功,但重命名失败!", HintType.Green) + End If + Case LoadState.Failed + MyMsgBox(CurrentLoader.Error.GetDisplay(True), CurrentLoader.Name & "失败", IsWarn:=True) + Case LoadState.Canceled + Hint(CurrentLoader.Name & "已取消!", HintType.Blue) + End Select + End Sub} + Dim Worker As New RedoableWorker(Sub() Loader.WaitForExit(Instance.PathVersion)) + '启动 + If McInstanceLock.TryAcquire(Worker, Instance.PathVersion) Then + AddHandler Worker.Stopped, Sub() RunInUi(Sub() LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\")) + Worker.Start() + '加锁后刷新版本列表,隐藏正在修改的实例。 + LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") + Else + Logger.Error("版本被其他 Worker 占用,版本修改失败") + Return False + End If + LoaderTaskbarAdd(Loader) + FrmMain.BtnExtraDownload.ShowRefresh() + FrmMain.BtnExtraDownload.Ribble() + Return True + Catch ex As Exception + LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") + Logger.Error(ex, "开始修改版本失败", LogBehavior.Toast) + Return False + End Try + End Function + + ''' + ''' 将安装好的临时版本文件夹覆盖到一个现有文件夹中。 + ''' + Public Sub McInstallCommit(Instance As McInstance, NewManifest As McInstallManifest, BuildFolder As String, TargetFolder As String) + Dim OldFiles = If(Configs.McInstallManifest.Get(Instance.Config)?.ManagedFiles, {}) + Dim NewFiles = NewManifest.ManagedFiles + + '============== 整理变更的文件列表 ============== + + Dim FilesToDelete As New List(Of String) + Dim FilesToMove As New List(Of (Source As String, Target As String)) + 'ManagedFiles + '新版里有:添加或覆盖新版文件 + For Each NewFile In NewFiles + FilesToMove.Add((Path.Combine(BuildFolder, NewFile.RelativePath), Path.Combine(TargetFolder, NewFile.RelativePath))) + Next + '新版里没有,老版里有:删除本地文件到回收站 + Dim NewFilePaths As New HashSet(Of String)(NewFiles.Select(Function(File) File.RelativePath)) + For Each OldFile In OldFiles + If NewFilePaths.Contains(OldFile.RelativePath) Then Continue For + FilesToDelete.Add(PathUtils.ForCompare(Path.Combine(TargetFolder, OldFile.RelativePath))) + Next + '老版本 MC 的 assets 文件 + If DirectoryUtils.Exists(BuildFolder & "resources\") Then DirectoryUtils.Copy(BuildFolder & "resources\", TargetFolder & "resources\") + 'json 与 jar + Dim OldJsonPath As String = Instance.GetJsonPath() + FilesToDelete.Add(OldJsonPath) + FilesToDelete.Add(Path.ChangeExtension(OldJsonPath, ".jar")) + FilesToDelete.Add(Instance.PathVersion & Instance.Name & ".jar") + FilesToDelete = FilesToDelete.Distinct(StringComparer.OrdinalIgnoreCase).ToList() + + '============== 实际修改 ============== + + Instance.Config.Save() + For Each TargetPath In FilesToDelete + FileUtils.Delete(TargetPath, True) + Next + FileUtils.Move(BuildFolder & Instance.Name & ".jar", Instance.PathVersion & Instance.Name & ".jar") + FileUtils.Move(BuildFolder & Instance.Name & ".json", OldJsonPath) + For Each File In FilesToMove + DirectoryUtils.Create(PathUtils.RemoveLastPart(File.Target)) + FileUtils.Copy(File.Source, File.Target) + Next + Dim ManagedFiles = NewFiles.Select(Function(File) New McInstallManifest.ManagedFile(File.RelativePath, File.Sha1, + FileUtils.GetInfo(Path.Combine(TargetFolder, File.RelativePath)).LastWriteTimeUtc, File.Component)).ToList() + Configs.McInstallManifest.Set(New McInstallManifest(NewManifest.SchemaVersion, NewManifest.Components, NewManifest.LastModifiedUtc, ManagedFiles), Instance.Config) + Instance.Config.Save() + End Sub + ''' ''' 在加载器状态改变后显示一条提示。 ''' 不会进行任何其他操作。 @@ -1813,345 +2144,22 @@ Retry: LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") End Sub Public Sub McInstallFailedClearFolder(Loader) - Try - If Loader.State <> LoadState.Failed AndAlso Loader.State <> LoadState.Canceled Then Return - Thread.Sleep(1000) '防止存在尚未完全释放的文件,导致清理失败(例如整合包安装) - '删除版本文件夹 - If DirectoryUtils.Exists(Loader.Input & "saves\") OrElse DirectoryUtils.Exists(Loader.Input & "versions\") Then - Logger.Warn($"由于版本已被独立启动,不清理版本文件夹:{Loader.Input}") - Else - Logger.Warn($"由于下载失败或取消,清理版本文件夹:{Loader.Input}") - DirectoryUtils.Delete(Loader.Input) - End If - Catch ex As Exception - Logger.Warn(ex, "下载失败或取消后清理版本文件夹失败") - End Try - End Sub - - ''' - ''' 进行合并安装。返回是否已经开始安装(例如如果没有安装 Java 则会进行提示并返回 False) - ''' - Public Function McInstall(Request As McInstallRequest) As Boolean - Try - Dim SubLoaders = McInstallLoader(Request) - If SubLoaders Is Nothing Then Return False - Dim Loader As New LoaderCombo(Of String)(Request.NewInstanceName & " 安装", SubLoaders) With {.OnStateChanged = AddressOf McInstallState} - - '启动 - Loader.Start(Request.VersionFolder) - LoaderTaskbarAdd(Loader) - FrmMain.BtnExtraDownload.ShowRefresh() - FrmMain.BtnExtraDownload.Ribble() - Return True - - Catch ex As Exception - Logger.Error(ex, "开始合并安装失败") - Return False - End Try - End Function - ''' - ''' 获取合并安装加载器列表,并进行前期的缓存清理与 Java 检查工作。 - ''' - Public Function McInstallLoader(Request As McInstallRequest) As List(Of LoaderBase) - '获取缓存目录(安装 Mod 加载器的文件夹不能包含空格) - Dim TempMcFolder As String = RequestTaskTempFolder(Request.OptiFineEntry IsNot Nothing OrElse Request.ForgeEntry IsNot Nothing OrElse Request.NeoForgeEntry IsNot Nothing) - - '获取参数 - Dim VersionFolder As String = McFolderSelected & "versions\" & Request.NewInstanceName & "\" - If DirectoryUtils.Exists(TempMcFolder) Then DirectoryUtils.Delete(TempMcFolder) - Dim OptiFineFolder As String = Nothing - If Request.OptiFineVersion IsNot Nothing Then - If Request.OptiFineVersion.Contains("_HD_U_") Then Request.OptiFineVersion = "HD_U_" & Request.OptiFineVersion.AfterLast("_HD_U_") '#735 - Request.OptiFineEntry = New DlOptiFineListEntry With { - .DisplayName = Request.MinecraftName & " " & Request.OptiFineVersion.Replace("HD_U_", "").Replace("_", "").Replace("pre", " pre"), - .Inherit = Request.MinecraftName, - .IsPreview = Request.OptiFineVersion.ContainsIgnoreCase("pre"), - .InstanceName = Request.MinecraftName & "-OptiFine_" & Request.OptiFineVersion, - .FileName = If(Request.OptiFineVersion.ContainsIgnoreCase("pre"), "preview_", "") & - "OptiFine_" & Request.MinecraftName & "_" & Request.OptiFineVersion & ".jar" - } - End If - If Request.OptiFineEntry IsNot Nothing Then OptiFineFolder = TempMcFolder & "versions\" & Request.OptiFineEntry.InstanceName - Dim ForgeFolder As String = Nothing - If Request.ForgeEntry IsNot Nothing Then Request.ForgeVersion = If(Request.ForgeVersion, Request.ForgeEntry.VersionName) - If Request.ForgeVersion IsNot Nothing Then ForgeFolder = TempMcFolder & "versions\forge-" & Request.ForgeVersion - Dim NeoForgeFolder As String = Nothing - If Request.NeoForgeEntry IsNot Nothing Then Request.NeoForgeVersion = If(Request.NeoForgeVersion, Request.NeoForgeEntry.VersionName) - If Request.NeoForgeVersion IsNot Nothing Then NeoForgeFolder = TempMcFolder & "versions\neoforge-" & Request.NeoForgeVersion - Dim FabricFolder As String = Nothing - If Request.FabricVersion IsNot Nothing Then FabricFolder = TempMcFolder & "versions\fabric-loader-" & Request.FabricVersion & "-" & Request.MinecraftName - Dim LiteLoaderFolder As String = Nothing - If Request.LiteLoaderEntry IsNot Nothing Then LiteLoaderFolder = TempMcFolder & "versions\" & Request.MinecraftName & "-LiteLoader" - - '判断 OptiFine 是否作为 Mod 进行下载 - Dim Modable As Boolean = Request.FabricVersion IsNot Nothing OrElse Request.ForgeEntry IsNot Nothing OrElse Request.NeoForgeEntry IsNot Nothing OrElse Request.LiteLoaderEntry IsNot Nothing - Dim ModsTempFolder As String = TempMcFolder & "mods\" - Dim OptiFineAsMod As Boolean = Request.OptiFineEntry IsNot Nothing AndAlso Modable '选择了 OptiFine 与任意 Mod 加载器 - If OptiFineAsMod Then - Logger.Info("OptiFine 将作为 Mod 进行下载") - If Request.LiteLoaderEntry IsNot Nothing Then - OptiFineFolder = ModsTempFolder & Request.MinecraftName & "\" '#8147 - Else - OptiFineFolder = ModsTempFolder - End If - End If - - '记录日志 - If OptiFineFolder IsNot Nothing Then Logger.Info($"OptiFine 缓存:{OptiFineFolder}") - If ForgeFolder IsNot Nothing Then Logger.Info($"Forge 缓存:{ForgeFolder}") - If NeoForgeFolder IsNot Nothing Then Logger.Info($"NeoForge 缓存:{NeoForgeFolder}") - If FabricFolder IsNot Nothing Then Logger.Info($"Fabric 缓存:{FabricFolder}") - If LiteLoaderFolder IsNot Nothing Then Logger.Info($"LiteLoader 缓存:{LiteLoaderFolder}") - Logger.Info($"对应的原版版本:{Request.MinecraftName}") - - '重复版本检查 - If FileUtils.Exists($"{VersionFolder}{Request.NewInstanceName}.json") Then - Hint("版本 " & Request.NewInstanceName & " 已经存在!", HintType.Red) - Throw New OperationCanceledException - End If - - Dim LoaderList As New List(Of LoaderBase) - '添加忽略标识 - LoaderList.Add(New LoaderTask(Of Integer, Integer)("添加忽略标识", Sub() FileUtils.Write(VersionFolder & ".pclignore", "用于临时地在 PCL 的版本列表中屏蔽此版本。")) With {.Show = False, .Block = False}) - 'Fabric API - If Request.FabricApi IsNot Nothing Then - LoaderList.Add(New LoaderDownload("下载 Fabric API", New List(Of NetFile) From {Request.FabricApi.ToNetFile(ModsTempFolder, ResourceVersion.DownloadReason.Dependency, Request.MinecraftName, ModLoaders.Fabric)}) With {.ProgressWeight = 3, .Block = False}) - End If - 'OptiFabric - If Request.OptiFabric IsNot Nothing Then - LoaderList.Add(New LoaderDownload("下载 OptiFabric", New List(Of NetFile) From {Request.OptiFabric.ToNetFile(ModsTempFolder, ResourceVersion.DownloadReason.Dependency, Request.MinecraftName, ModLoaders.Fabric)}) With {.ProgressWeight = 3, .Block = False}) - End If - '原版 - Dim ClientLoader = New LoaderCombo(Of String)("下载原版 " & Request.MinecraftName, McDownloadClientLoader(Request.MinecraftName, Request.MinecraftJson, Request.NewInstanceName)) With {.Show = False, .ProgressWeight = 39, - .Block = Request.ForgeVersion Is Nothing AndAlso Request.NeoForgeVersion Is Nothing AndAlso Request.OptiFineEntry Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing} - LoaderList.Add(ClientLoader) - 'OptiFine - If Request.OptiFineEntry IsNot Nothing Then - If OptiFineAsMod Then - LoaderList.Add(New LoaderCombo(Of String)("下载 OptiFine " & Request.OptiFineEntry.DisplayName, McDownloadOptiFineSaveLoader(Request.OptiFineEntry, OptiFineFolder & Request.OptiFineEntry.FileName)) With {.Show = False, .ProgressWeight = 16, - .Block = Request.ForgeVersion Is Nothing AndAlso Request.NeoForgeVersion Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing}) - Else - LoaderList.Add(New LoaderCombo(Of String)("下载 OptiFine " & Request.OptiFineEntry.DisplayName, McDownloadOptiFineLoader(Request.OptiFineEntry, TempMcFolder, ClientLoader, Request.VersionFolder)) With {.Show = False, .ProgressWeight = 24, - .Block = Request.ForgeVersion Is Nothing AndAlso Request.NeoForgeVersion Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing}) - End If - End If - 'Forge - If Request.ForgeVersion IsNot Nothing Then - LoaderList.Add(New LoaderCombo(Of String)("下载 Forge " & Request.ForgeVersion, McDownloadForgelikeLoader(False, Request.ForgeVersion, "forge-" & Request.ForgeVersion, Request.MinecraftName, Request.ForgeEntry, TempMcFolder, ClientLoader, Request.VersionFolder)) With {.Show = False, .ProgressWeight = 25, - .Block = Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing AndAlso Request.NeoForgeEntry Is Nothing}) - End If - 'NeoForge - If Request.NeoForgeVersion IsNot Nothing Then - LoaderList.Add(New LoaderCombo(Of String)("下载 NeoForge " & Request.NeoForgeVersion, McDownloadForgelikeLoader(True, Request.NeoForgeVersion, "neoforge-" & Request.NeoForgeVersion, Request.MinecraftName, Request.NeoForgeEntry, TempMcFolder, ClientLoader, Request.VersionFolder)) With {.Show = False, .ProgressWeight = 25, - .Block = Request.ForgeEntry Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing}) - End If - 'LiteLoader - If Request.LiteLoaderEntry IsNot Nothing Then - LoaderList.Add(New LoaderCombo(Of String)("下载 LiteLoader " & Request.MinecraftName, McDownloadLiteLoaderLoader(Request.LiteLoaderEntry, TempMcFolder)) With {.Show = False, .ProgressWeight = 1, - .Block = Request.FabricVersion Is Nothing}) - End If - 'Fabric - If Request.FabricVersion IsNot Nothing Then - LoaderList.Add(New LoaderCombo(Of String)("下载 Fabric " & Request.FabricVersion, McDownloadFabricLoader(Request.FabricVersion, Request.MinecraftName, TempMcFolder)) With {.Show = False, .ProgressWeight = 2, - .Block = True}) - End If - '合并安装 - LoaderList.Add(New LoaderTask(Of String, String)("安装游戏", - Sub(Task As LoaderTask(Of String, String)) - '合并 JSON - MergeJson(VersionFolder, VersionFolder, OptiFineFolder, OptiFineAsMod, ForgeFolder, Request.ForgeVersion, NeoForgeFolder, Request.NeoForgeVersion, FabricFolder, LiteLoaderFolder) - Task.Progress = 0.2 - '迁移文件 - DirectoryUtils.Copy(TempMcFolder & "libraries", McFolderSelected & "libraries") - Task.Progress = 0.8 - '创建 Mod 和资源包文件夹 - Dim ModsFolder = New McInstance(VersionFolder).PathIndie & "mods\" '版本隔离信息在此时被决定 - If DirectoryUtils.Exists(ModsTempFolder) Then - DirectoryUtils.Copy(ModsTempFolder, ModsFolder) - ElseIf Modable Then - DirectoryUtils.Create(ModsFolder) - Logger.Info($"自动创建 Mod 文件夹:{ModsFolder}") - End If - Dim ResourcepacksFolder = New McInstance(VersionFolder).PathIndie & "resourcepacks\" - DirectoryUtils.Create(ResourcepacksFolder) - Logger.Info($"自动创建资源包文件夹:{ResourcepacksFolder}") - End Sub) With {.ProgressWeight = 2, .Block = True}) - '补全文件 - If Request.OptiFineEntry IsNot Nothing OrElse (Request.ForgeVersion IsNot Nothing AndAlso Request.ForgeVersion.BeforeFirst(".") >= 20) OrElse Request.NeoForgeVersion IsNot Nothing OrElse Request.FabricVersion IsNot Nothing OrElse Request.LiteLoaderEntry IsNot Nothing Then - Dim LoadersLib As New List(Of LoaderBase) - LoadersLib.Add(New LoaderTask(Of String, List(Of NetFile))("分析游戏支持库文件(副加载器)", Sub(Task) Task.Output = McLibNetFilesFromInstance(New McInstance(VersionFolder))) With {.ProgressWeight = 1, .Show = False}) - LoadersLib.Add(New LoaderDownload("下载游戏支持库文件(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 7, .Show = False}) - LoaderList.Add(New LoaderCombo(Of String)("下载游戏支持库文件", LoadersLib) With {.ProgressWeight = 8}) - End If - '删除忽略标识 - LoaderList.Add(New LoaderTask(Of Integer, Integer)("删除忽略标识", Sub() FileUtils.Delete(VersionFolder & ".pclignore")) With {.Show = False}) - '总加载器 - Return LoaderList - End Function - - ''' - ''' 将多个版本 JSON 进行合并,如果目标已存在则直接覆盖。失败会抛出异常。 - ''' - Private Sub MergeJson(OutputFolder As String, MinecraftFolder As String, Optional OptiFineFolder As String = Nothing, Optional OptiFineAsMod As Boolean = False, Optional ForgeFolder As String = Nothing, Optional ForgeVersion As String = Nothing, Optional NeoForgeFolder As String = Nothing, Optional NeoForgeVersion As String = Nothing, Optional FabricFolder As String = Nothing, Optional LiteLoaderFolder As String = Nothing) - Logger.Info($"开始进行版本合并,输出:{OutputFolder},Minecraft:{MinecraftFolder}" & - If(OptiFineFolder IsNot Nothing, ",OptiFine:" & OptiFineFolder, "") & - If(ForgeFolder IsNot Nothing, ",Forge:" & ForgeFolder, "") & - If(NeoForgeFolder IsNot Nothing, ",NeoForge:" & NeoForgeFolder, "") & - If(LiteLoaderFolder IsNot Nothing, ",LiteLoader:" & LiteLoaderFolder, "") & - If(FabricFolder IsNot Nothing, ",Fabric:" & FabricFolder, "")) - DirectoryUtils.Create(OutputFolder) - - Dim HasOptiFine As Boolean = OptiFineFolder IsNot Nothing AndAlso Not OptiFineAsMod, HasForge As Boolean = ForgeFolder IsNot Nothing, HasNeoForge As Boolean = NeoForgeFolder IsNot Nothing, HasLiteLoader As Boolean = LiteLoaderFolder IsNot Nothing, HasFabric As Boolean = FabricFolder IsNot Nothing - Dim OutputName As String, MinecraftName As String, OptiFineName As String, ForgeName As String, NeoForgeName As String, LiteLoaderName As String, FabricName As String - Dim OutputJsonPath As String, MinecraftJsonPath As String, OptiFineJsonPath As String = Nothing, ForgeJsonPath As String = Nothing, NeoForgeJsonPath As String = Nothing, LiteLoaderJsonPath As String = Nothing, FabricJsonPath As String = Nothing - Dim OutputJar As String, MinecraftJar As String -#Region "初始化路径信息" - If Not OutputFolder.EndsWithF("\") Then OutputFolder += "\" - OutputName = PathUtils.GetLastPart(OutputFolder) - OutputJsonPath = OutputFolder & OutputName & ".json" - OutputJar = OutputFolder & OutputName & ".jar" - - If Not MinecraftFolder.EndsWithF("\") Then MinecraftFolder += "\" - MinecraftName = PathUtils.GetLastPart(MinecraftFolder) - MinecraftJsonPath = MinecraftFolder & MinecraftName & ".json" - MinecraftJar = MinecraftFolder & MinecraftName & ".jar" - - If HasOptiFine Then - If Not OptiFineFolder.EndsWithF("\") Then OptiFineFolder += "\" - OptiFineName = PathUtils.GetLastPart(OptiFineFolder) - OptiFineJsonPath = OptiFineFolder & OptiFineName & ".json" - End If - - If HasForge Then - If Not ForgeFolder.EndsWithF("\") Then ForgeFolder += "\" - ForgeName = PathUtils.GetLastPart(ForgeFolder) - ForgeJsonPath = ForgeFolder & ForgeName & ".json" - End If - - If HasNeoForge Then - If Not NeoForgeFolder.EndsWithF("\") Then NeoForgeFolder += "\" - NeoForgeName = PathUtils.GetLastPart(NeoForgeFolder) - NeoForgeJsonPath = NeoForgeFolder & NeoForgeName & ".json" - End If - - If HasLiteLoader Then - If Not LiteLoaderFolder.EndsWithF("\") Then LiteLoaderFolder += "\" - LiteLoaderName = PathUtils.GetLastPart(LiteLoaderFolder) - LiteLoaderJsonPath = LiteLoaderFolder & LiteLoaderName & ".json" - End If - - If HasFabric Then - If Not FabricFolder.EndsWithF("\") Then FabricFolder += "\" - FabricName = PathUtils.GetLastPart(FabricFolder) - FabricJsonPath = FabricFolder & FabricName & ".json" - End If -#End Region - - Dim OutputJson As JObject, MinecraftJson As JObject, OptiFineJson As JObject = Nothing, ForgeJson As JObject = Nothing, NeoForgeJson As JObject = Nothing, LiteLoaderJson As JObject = Nothing, FabricJson As JObject = Nothing -#Region "读取文件并检查文件是否合规" - Dim MinecraftJsonText As String = If(FileUtils.TryReadAsString(MinecraftJsonPath), "") - If Not MinecraftJsonText.StartsWithF("{") Then Throw New Exception("Minecraft json 有误,地址:" & MinecraftJsonPath & ",前段内容:" & MinecraftJsonText.Substring(0, Math.Min(MinecraftJsonText.Length, 1000))) - MinecraftJson = MinecraftJsonText.DeserializeJson() - - If HasOptiFine Then - Dim OptiFineJsonText As String = If(FileUtils.TryReadAsString(OptiFineJsonPath), "") - If Not OptiFineJsonText.StartsWithF("{") Then Throw New Exception("OptiFine json 有误,地址:" & OptiFineJsonPath & ",前段内容:" & OptiFineJsonText.Substring(0, Math.Min(OptiFineJsonText.Length, 1000))) - OptiFineJson = OptiFineJsonText.DeserializeJson() - End If - - If HasForge Then - Dim ForgeJsonText As String = If(FileUtils.TryReadAsString(ForgeJsonPath), "") - If Not ForgeJsonText.StartsWithF("{") Then Throw New Exception("Forge json 有误,地址:" & ForgeJsonPath & ",前段内容:" & ForgeJsonText.Substring(0, Math.Min(ForgeJsonText.Length, 1000))) - ForgeJson = ForgeJsonText.DeserializeJson() - End If - - If HasNeoForge Then - Dim NeoForgeJsonText As String = If(FileUtils.TryReadAsString(NeoForgeJsonPath), "") - If Not NeoForgeJsonText.StartsWithF("{") Then Throw New Exception("NeoForge json 有误,地址:" & NeoForgeJsonPath & ",前段内容:" & NeoForgeJsonText.Substring(0, Math.Min(NeoForgeJsonText.Length, 1000))) - NeoForgeJson = NeoForgeJsonText.DeserializeJson() - End If - - If HasLiteLoader Then - Dim LiteLoaderJsonText As String = If(FileUtils.TryReadAsString(LiteLoaderJsonPath), "") - If Not LiteLoaderJsonText.StartsWithF("{") Then Throw New Exception("LiteLoader json 有误,地址:" & LiteLoaderJsonPath & ",前段内容:" & LiteLoaderJsonText.Substring(0, Math.Min(LiteLoaderJsonText.Length, 1000))) - LiteLoaderJson = LiteLoaderJsonText.DeserializeJson() - End If - - If HasFabric Then - Dim FabricJsonText As String = If(FileUtils.TryReadAsString(FabricJsonPath), "") - If Not FabricJsonText.StartsWithF("{") Then Throw New Exception("Fabric json 有误,地址:" & FabricJsonPath & ",前段内容:" & FabricJsonText.Substring(0, Math.Min(FabricJsonText.Length, 1000))) - FabricJson = FabricJsonText.DeserializeJson() - End If -#End Region - -#Region "处理 JSON 文件" - '获取 minecraftArguments - Dim AllArguments As String = - If(MinecraftJson("minecraftArguments"), " ").ToString & " " & - If(OptiFineJson IsNot Nothing, If(OptiFineJson("minecraftArguments"), " ").ToString, " ") & " " & - If(ForgeJson IsNot Nothing, If(ForgeJson("minecraftArguments"), " ").ToString, " ") & " " & - If(NeoForgeJson IsNot Nothing, If(NeoForgeJson("minecraftArguments"), " ").ToString, " ") & " " & - If(LiteLoaderJson IsNot Nothing, If(LiteLoaderJson("minecraftArguments"), " ").ToString, " ") - '分割参数字符串 - Dim RawArguments As List(Of String) = AllArguments.Split(" ", True).Select(Function(l) l.Trim).ToList - Dim SplitArguments As New List(Of String) - For i = 0 To RawArguments.Count - 1 - If RawArguments(i).StartsWithF("-") Then - SplitArguments.Add(RawArguments(i)) - ElseIf SplitArguments.Any AndAlso SplitArguments.Last.StartsWithF("-") AndAlso Not SplitArguments.Last.Contains(" ") Then - SplitArguments(SplitArguments.Count - 1) = SplitArguments.Last & " " & RawArguments(i) - Else - SplitArguments.Add(RawArguments(i)) - End If - Next - Dim RealArguments As String = SplitArguments.Distinct.Join(" ") - '合并 - '相关讨论见 #2801 - OutputJson = MinecraftJson - If HasOptiFine Then - '合并 OptiFine - OptiFineJson.Remove("releaseTime") - OptiFineJson.Remove("time") - OutputJson.Merge(OptiFineJson) - End If - If HasForge Then - '合并 Forge - ForgeJson.Remove("releaseTime") - ForgeJson.Remove("time") - OutputJson.Merge(ForgeJson) - End If - If HasNeoForge Then - '合并 NeoForge - NeoForgeJson.Remove("releaseTime") - NeoForgeJson.Remove("time") - OutputJson.Merge(NeoForgeJson) - End If - If HasLiteLoader Then - '合并 LiteLoader - LiteLoaderJson.Remove("releaseTime") - LiteLoaderJson.Remove("time") - OutputJson.Merge(LiteLoaderJson) - End If - If HasFabric Then - '合并 Fabric - FabricJson.Remove("releaseTime") - FabricJson.Remove("time") - OutputJson.Merge(FabricJson) - End If - '修改 - If RealArguments IsNot Nothing AndAlso RealArguments.Replace(" ", "") <> "" Then OutputJson("minecraftArguments") = RealArguments - OutputJson.Remove("_comment_") - OutputJson.Remove("inheritsFrom") - OutputJson.Remove("jar") - OutputJson("id") = OutputName -#End Region - -#Region "保存" - FileUtils.Write(OutputJsonPath, OutputJson.ToString) - FileUtils.Copy(MinecraftJar, OutputJar) - Logger.Info($"版本合并 {OutputName} 完成") -#End Region - + If Loader.State <> LoadState.Failed AndAlso Loader.State <> LoadState.Canceled Then Return + RunInThread( + Sub() + Try + Thread.Sleep(1000) '防止存在尚未完全释放的文件,导致清理失败(例如整合包安装) + '删除版本文件夹 + If DirectoryUtils.Exists(Loader.Input & "saves\") OrElse DirectoryUtils.Exists(Loader.Input & "versions\") Then + Logger.Warn($"由于版本已被独立启动,不清理版本文件夹:{Loader.Input}") + Else + Logger.Warn($"由于下载失败或取消,清理版本文件夹:{Loader.Input}") + DirectoryUtils.Delete(Loader.Input) + End If + Catch ex As Exception + Logger.Warn(ex, "下载失败或取消后清理版本文件夹失败") + End Try + End Sub) End Sub #End Region diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml index 2e97def..6a8a852 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml +++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml @@ -1,7 +1,7 @@ @@ -14,19 +14,96 @@ + + + + + M9.83,3.17 A1.18,1.18 0 0 1 9.83,4.83 L6.85,7.82 H14.5 A6.68,6.68 0 0 1 14.5,21.18 H11 A1.18,1.18 0 0 1 11,18.82 H14.5 A4.32,4.32 0 0 0 14.5,10.18 H6.85 L9.83,13.17 A1.18,1.18 0 0 1 8.17,14.83 L3.17,9.83 A1.18,1.18 0 0 1 3.17,8.17 L8.17,3.17 A1.18,1.18 0 0 1 9.83,3.17 Z + F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z + + + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + M2,4 l-2,2 10,10 10,-10 -2,-2 -8,8 -8,-8 z + + + + + @@ -37,13 +114,25 @@ - + - - + + + + + + + + + + + + + + + @@ -55,13 +144,25 @@ - + - - + + + + + + + + + + + + + + + @@ -73,13 +174,25 @@ - + - - + + + + + + + + + + + + + + + @@ -91,13 +204,25 @@ - + - - + + + + + + + + + + + + + + + @@ -111,13 +236,25 @@ - + - - + + + + + + + + + + + + + + + @@ -129,13 +266,25 @@ - + - - + + + + + + + + + + + + + + + @@ -147,13 +296,25 @@ - + - - + + + + + + + + + + + + + + + @@ -163,7 +324,7 @@ LogoScale="0.95" Logo="M955 610h-59c-15 0-29 13-29 29v196c0 15-13 29-29 29h-649c-15 0-29-13-29-29v-196c0-15-13-29-29-29h-59c-15 0-29 13-29 29V905c0 43 35 78 78 78h787c43 0 78-35 78-78V640c0-15-13-29-29-29zM492 740c11 11 29 11 41 0l265-265c11-11 11-29 0-41l-41-41c-11-11-29-11-41 0l-110 110c-11 11-33 3-33-13V68C571 53 555 39 541 39h-59c-15 0-29 13-29 29v417c0 17-21 25-33 13l-110-110c-11-11-29-11-41 0L226 433c-11 11-11 29 0 41L492 740z" /> - + @@ -196,4 +357,4 @@ ---> \ No newline at end of file +--> diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb index 64b04c5..2ce085e 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb @@ -1,12 +1,30 @@ Public Class PageDownloadInstall - Private Sub LoaderInit() Handles Me.Initialized + Public Sub ControlInit() DisabledPageAnimControls.Add(BtnStart) + LoadOptiFine.State = DlOptiFineListLoader + LoadLiteLoader.State = DlLiteLoaderListLoader + LoadFabric.State = DlFabricListLoader + LoadFabricApi.State = DlFabricApiLoader + LoadNeoForge.State = DlNeoForgeListLoader + LoadOptiFabric.State = DlOptiFabricLoader + + McDownloadForgeRecommendedRefresh() + End Sub + + Public Overridable Sub LoaderInit() Handles Me.Initialized + ControlInit() PageLoaderInit(LoadMinecraft, PanLoad, PanAllBack, Nothing, DlClientListLoader, AddressOf LoadMinecraft_OnFinish) End Sub Private IsLoad As Boolean = False - Private Sub Init() Handles Me.Loaded + Public Overridable Sub Init() Handles Me.Loaded + If IsLoad Then + IsSelectNameEdited = False + ExitSelectPage() + If DlClientListLoader.State = LoadState.Finished Then TrySelectWaitingVersion() + End If + IsLoad = True PanBack.ScrollToHome() DlOptiFineListLoader.Start() DlLiteLoaderListLoader.Start() @@ -17,35 +35,19 @@ Public Class PageDownloadInstall TextSelectName.ValidateRules = New ObjectModel.Collection(Of Validate) From {New ValidateFolderName(McFolderSelected & "versions")} TextSelectName.Validate() ReloadSelected() - - '非重复加载部分 - If IsLoad Then Return - IsLoad = True - - McDownloadForgeRecommendedRefresh() - - LoadOptiFine.State = DlOptiFineListLoader - LoadLiteLoader.State = DlLiteLoaderListLoader - LoadFabric.State = DlFabricListLoader - LoadFabricApi.State = DlFabricApiLoader - LoadNeoForge.State = DlNeoForgeListLoader - LoadOptiFabric.State = DlOptiFabricLoader End Sub #Region "页面切换" '页面切换动画 Public IsInSelectPage As Boolean = False - Private IsFirstLoaded As Boolean = False - Private Sub EnterSelectPage() - If IsInSelectPage Then Return + + ''' + ''' 选择页面 UI 初始化。 + ''' + Public Sub PrepareSelectPage() IsInSelectPage = True - PanInner.Margin = New Thickness(25, 10, 25, 40) - - AutoSelectedFabricApi = False - AutoSelectedOptiFabric = False - IsSelectNameEdited = False PanSelect.Visibility = Visibility.Visible PanSelect.IsHitTestVisible = True PanMinecraft.IsHitTestVisible = False @@ -54,13 +56,9 @@ Public Class PageDownloadInstall DisabledPageAnimControls.Remove(BtnStart) BtnStart.Show = True - CardOptiFine.IsSwapped = True - CardLiteLoader.IsSwapped = True - CardForge.IsSwapped = True - CardNeoForge.IsSwapped = True - CardFabric.IsSwapped = True - CardFabricApi.IsSwapped = True - CardOptiFabric.IsSwapped = True + For Each Card In {CardOptiFine, CardLiteLoader, CardForge, CardNeoForge, CardFabric, CardFabricApi, CardOptiFabric} + Card.IsSwapped = True + Next '如果在选择页面按了刷新键,选择页的东西可能会由于动画被隐藏,但不会由于加载结束而再次显示,因此这里需要手动恢复 For Each Control In GetAllAnimControls(PanSelect) @@ -69,9 +67,16 @@ Public Class PageDownloadInstall Control.RenderTransform = New TranslateTransform End If Next + End Sub + + Private Sub EnterSelectPage() + If IsInSelectPage Then Return + AutoSelectedFabricApi = False + AutoSelectedOptiFabric = False + PrepareSelectPage() '启动 Forge 加载 - If McVersion.IsFormatFit(VanillaName) Then + If McVersion.IsStandardFormat(VanillaName) Then Dim ForgeLoader = New LoaderTask(Of String, List(Of DlForgeVersionEntry))("DlForgeVersion " & VanillaName, AddressOf DlForgeVersionMain) LoadForge.State = ForgeLoader ForgeLoader.Start(VanillaName) @@ -87,35 +92,27 @@ Public Class PageDownloadInstall AaCode( Sub() PanBack.ScrollToHome() - TextSelectName.Validate() - OptiFine_Loaded() - LiteLoader_Loaded() - Forge_Loaded() - NeoForge_Loaded() - Fabric_Loaded() - FabricApi_Loaded() - OptiFabric_Loaded() - ReloadSelected() + ReloadComponentLists() PanMinecraft.Visibility = Visibility.Collapsed End Sub, After:=True), AaOpacity(PanSelect, 1 - PanSelect.Opacity, 70, 100), AaTranslateX(PanSelect, -CType(PanSelect.RenderTransform, TranslateTransform).X, 160, 100, Ease:=New AniEaseOutFluent(AniEasePower.ExtraStrong)), - AaCode( - Sub() - PanBack.IsHitTestVisible = True - '初始化 Binding - If IsFirstLoaded Then Return - IsFirstLoaded = True - BtnOptiFineClearInner.SetBinding(Shapes.Path.FillProperty, New Binding("Foreground") With {.Source = CardOptiFine.MainTextBlock, .Mode = BindingMode.OneWay}) - BtnLiteLoaderClearInner.SetBinding(Shapes.Path.FillProperty, New Binding("Foreground") With {.Source = CardLiteLoader.MainTextBlock, .Mode = BindingMode.OneWay}) - BtnForgeClearInner.SetBinding(Shapes.Path.FillProperty, New Binding("Foreground") With {.Source = CardForge.MainTextBlock, .Mode = BindingMode.OneWay}) - BtnNeoForgeClearInner.SetBinding(Shapes.Path.FillProperty, New Binding("Foreground") With {.Source = CardNeoForge.MainTextBlock, .Mode = BindingMode.OneWay}) - BtnFabricClearInner.SetBinding(Shapes.Path.FillProperty, New Binding("Foreground") With {.Source = CardFabric.MainTextBlock, .Mode = BindingMode.OneWay}) - BtnFabricApiClearInner.SetBinding(Shapes.Path.FillProperty, New Binding("Foreground") With {.Source = CardFabricApi.MainTextBlock, .Mode = BindingMode.OneWay}) - BtnOptiFabricClearInner.SetBinding(Shapes.Path.FillProperty, New Binding("Foreground") With {.Source = CardOptiFabric.MainTextBlock, .Mode = BindingMode.OneWay}) - End Sub,, True) + AaCode(Sub() PanBack.IsHitTestVisible = True,, True) }, "FrmDownloadInstall SelectPageSwitch", True) End Sub + + Public Sub ReloadComponentLists() + TextSelectName.Validate() + OptiFine_Loaded() + LiteLoader_Loaded() + Forge_Loaded() + NeoForge_Loaded() + Fabric_Loaded() + FabricApi_Loaded() + OptiFabric_Loaded() + ReloadSelected() + End Sub + Public Sub ExitSelectPage() Handles BtnBack.Click If Not IsInSelectPage Then Return IsInSelectPage = False @@ -144,10 +141,11 @@ Public Class PageDownloadInstall End Sub,, True) }, "FrmDownloadInstall SelectPageSwitch") End Sub - Public Sub MinecraftSelected(sender As MyListItem, e As MouseButtonEventArgs) + Public Overridable Sub MinecraftSelected(sender As MyListItem, e As MouseButtonEventArgs) VanillaName = sender.Title VanillaData = sender.Tag VanillaIcon = sender.Logo + LabMinecraftVersion.Text = VanillaName EnterSelectPage() End Sub @@ -156,9 +154,9 @@ Public Class PageDownloadInstall #Region "选择" 'Minecraft - Private VanillaName As String - Private VanillaData As JObject - Private VanillaIcon As String + Public VanillaName As String + Public VanillaData As JObject + Public VanillaIcon As String Private ReadOnly Property VanillaDrop As Integer Get Return McVersion.VersionToDrop(VanillaName) @@ -166,24 +164,25 @@ Public Class PageDownloadInstall End Property '附加组件 - Private SelectedOptiFine As DlOptiFineListEntry = Nothing - Private SelectedLiteLoader As DlLiteLoaderListEntry = Nothing - Private SelectedForge As DlForgeVersionEntry = Nothing - Private SelectedNeoForge As DlNeoForgeListEntry = Nothing - Private SelectedFabric As String = Nothing - Private SelectedFabricApi As ResourceVersion = Nothing - Private SelectedOptiFabric As ResourceVersion = Nothing + Public SelectedOptiFine As DlOptiFineListEntry = Nothing + Public SelectedLiteLoader As DlLiteLoaderListEntry = Nothing + Public SelectedForge As DlForgeVersionEntry = Nothing + Public SelectedNeoForge As DlNeoForgeListEntry = Nothing + Public SelectedFabric As String = Nothing + Public SelectedFabricApi As ResourceVersion = Nothing + Public SelectedOptiFabric As ResourceVersion = Nothing ''' ''' 重载已选择的项目的显示。 ''' - Private Sub ReloadSelected() Handles CardOptiFine.Swap, LoadOptiFine.StateChanged, CardForge.Swap, LoadForge.StateChanged, CardNeoForge.Swap, LoadNeoForge.StateChanged, CardFabric.Swap, LoadFabric.StateChanged, CardFabricApi.Swap, LoadFabricApi.StateChanged, CardOptiFabric.Swap, LoadOptiFabric.StateChanged, CardLiteLoader.Swap, LoadLiteLoader.StateChanged + Public Overridable Sub ReloadSelected() Handles CardOptiFine.Swap, LoadOptiFine.StateChanged, CardForge.Swap, LoadForge.StateChanged, CardNeoForge.Swap, LoadNeoForge.StateChanged, CardFabric.Swap, LoadFabric.StateChanged, CardFabricApi.Swap, LoadFabricApi.StateChanged, CardOptiFabric.Swap, LoadOptiFabric.StateChanged, CardLiteLoader.Swap, LoadLiteLoader.StateChanged Static Ongoing As Boolean = False '#3742 中,LoadOptiFineGetError 会初始化 LoadOptiFine,触发事件 LoadOptiFine.StateChanged,导致再次调用 SelectReload If VanillaName Is Nothing OrElse Ongoing Then Return Ongoing = True '主预览 SelectNameUpdate() ImgLogo.Source = GetSelectLogo() + ImgMinecraft.Source = VanillaIcon 'OptiFine Dim OptiFineError As String = LoadOptiFineGetError() CardOptiFine.MainSwap.Visibility = If(OptiFineError Is Nothing, Visibility.Visible, Visibility.Collapsed) @@ -222,7 +221,7 @@ Public Class PageDownloadInstall End If End If 'Forge - If Not McVersion.IsFormatFit(VanillaName) Then + If Not McVersion.IsStandardFormat(VanillaName) Then CardForge.Visibility = Visibility.Collapsed Else CardForge.Visibility = Visibility.Visible @@ -356,7 +355,7 @@ Public Class PageDownloadInstall ''' ''' 清空已选择的项目。 ''' - Private Sub ClearSelected() + Public Sub ClearSelected() VanillaName = Nothing VanillaData = Nothing VanillaIcon = Nothing @@ -367,7 +366,10 @@ Public Class PageDownloadInstall SelectedFabric = Nothing SelectedFabricApi = Nothing SelectedOptiFabric = Nothing - IsSelectNameEdited = False + For Each ClearButton In {BtnOptiFineClear, BtnLiteLoaderClear, BtnForgeClear, BtnNeoForgeClear, + BtnFabricClear, BtnFabricApiClear, BtnOptiFabricClear} + ClearButton.Visibility = Visibility.Collapsed + Next End Sub '信息栏动画 Private Sub SetPanelVisibility(Panel As Grid, IsVisible As Boolean) @@ -389,7 +391,7 @@ Public Class PageDownloadInstall ''' ''' 获取版本图标。 ''' - Private Function GetSelectLogo() As String + Public Overridable Function GetSelectLogo() As String If SelectedFabric IsNot Nothing Then Return "pack://application:,,,/images/Blocks/Fabric.png" ElseIf SelectedForge IsNot Nothing Then @@ -409,7 +411,7 @@ Public Class PageDownloadInstall ''' ''' 获取默认版本名。 ''' - Private Function GetSelectName() As String + Public Function GetSelectName() As String Dim Name As String = VanillaName If SelectedFabric IsNot Nothing Then Name += "-Fabric " & SelectedFabric.BeforeFirst("+") If SelectedForge IsNot Nothing Then Name += "-Forge_" & SelectedForge.VersionName @@ -418,36 +420,39 @@ Public Class PageDownloadInstall If SelectedOptiFine IsNot Nothing Then Name += "-OptiFine_" & SelectedOptiFine.DisplayName.Replace(VanillaName & " ", "").Replace(" ", "_") Return Name End Function - Private IsSelectNameEdited As Boolean = False - Private IsSelectNameChanging As Boolean = False + Public IsSelectNameEdited As Boolean = False Private Sub SelectNameUpdate() - If IsSelectNameEdited OrElse IsSelectNameChanging Then Return - IsSelectNameChanging = True + If IsSelectNameEdited Then Return TextSelectName.Text = GetSelectName() - IsSelectNameChanging = False + IsSelectNameEdited = False End Sub Private Sub TextSelectName_TextChanged(sender As Object, e As TextChangedEventArgs) Handles TextSelectName.TextChanged - If IsSelectNameChanging Then Return IsSelectNameEdited = True - ReloadSelected() End Sub Private Sub TextSelectName_ValidateChanged(sender As Object, e As EventArgs) Handles TextSelectName.ValidateChanged BtnStart.IsEnabled = TextSelectName.IsValidated + HintSelectName.Text = TextSelectName.ValidateResult + HintSelectName.Visibility = If(TextSelectName.IsValidated, Visibility.Collapsed, Visibility.Visible) End Sub #End Region #Region "加载器" + Public MinecraftVersions As JArray + Private Sub LoadMinecraft_OnFinish() + MinecraftVersions = CType(DlClientListLoader.Output.Value("versions").DeepClone(), JArray) + ShowMinecraftVersionList() + TrySelectWaitingVersion() '自动选择版本 + End Sub '结果数据化 - Private Sub LoadMinecraft_OnFinish() + Public Overridable Sub ShowMinecraftVersionList() ExitSelectPage() '返回 Try Dim Dict As New Dictionary(Of String, List(Of JObject)) From { {"正式版", New List(Of JObject)}, {"预览版", New List(Of JObject)}, {"远古版", New List(Of JObject)}, {"愚人节版", New List(Of JObject)} } - Dim Versions As JArray = DlClientListLoader.Output.Value("versions") - For Each Version As JObject In Versions + For Each Version As JObject In MinecraftVersions '确定分类 Dim Type As String = Version("type") Select Case Type @@ -498,7 +503,7 @@ Public Class PageDownloadInstall '清空当前 PanMinecraft.Children.Clear() '添加最新版本 - Dim CardInfo As New MyCard With {.Title = "最新版本", .Margin = New Thickness(0, 15, 0, 15), .SwapType = 2} + Dim CardInfo As New MyCard With {.Title = "最新版本", .Margin = New Thickness(0, 15, 0, 15)} Dim PinnedVersions As New List(Of JObject) Dim Release As JObject = Dict("正式版")(0).DeepClone() Release("lore") = "最新正式版,发布于 " & Release("releaseTime").Value(Of Date).ToString("yyyy'/'MM'/'dd HH':'mm") @@ -508,36 +513,41 @@ Public Class PageDownloadInstall Snapshot("lore") = "最新预览版,发布于 " & Snapshot("releaseTime").Value(Of Date).ToString("yyyy'/'MM'/'dd HH':'mm") PinnedVersions.Add(Snapshot) End If - Dim PanInfo As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = PinnedVersions} - MyCard.StackInstall(PanInfo, 7) + Dim PanInfo As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0)} CardInfo.Children.Add(PanInfo) + CardInfo.SwapControl = PanInfo + CardInfo.SetContentBuilder(PinnedVersions, '使用 Lambda 转发点击事件,避免 #535 + Function(Version) McDownloadListItem(Version, Sub(sender, e) MinecraftSelected(sender, e), False)) + CardInfo.BuildContent() PanMinecraft.Children.Insert(0, CardInfo) '添加其他版本 For Each Pair As KeyValuePair(Of String, List(Of JObject)) In Dict If Not Pair.Value.Any() Then Continue For '增加卡片 - Dim NewCard As New MyCard With {.Title = Pair.Key, .Margin = New Thickness(0, 0, 0, 15), .SwapType = 7} - Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = Pair.Value} + Dim NewCard As New MyCard With {.Title = Pair.Key, .Margin = New Thickness(0, 0, 0, 15)} + Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0)} NewCard.Children.Add(NewStack) NewCard.SwapControl = NewStack + NewCard.SetContentBuilder(Pair.Value, + Function(Version) McDownloadListItem(Version, Sub(sender, e) MinecraftSelected(sender, e), False)) NewCard.IsSwapped = True PanMinecraft.Children.Add(NewCard) Next - '自动选择版本 - If VersionWaitingSelect Is Nothing Then Exit Try - Logger.Info($"自动选择 MC 版本:{VersionWaitingSelect}") - For Each Version As JObject In Versions - If Version("id").ToString <> VersionWaitingSelect Then Continue For - Dim Item = McDownloadListItem(Version, Sub() - End Sub, False).Init() - MinecraftSelected(Item, Nothing) - VersionWaitingSelect = Nothing - Return - Next Catch ex As Exception Logger.Error(ex, "可视化安装版本列表出错") End Try End Sub + Private Sub TrySelectWaitingVersion() + If VersionWaitingSelect Is Nothing Then Return + For Each Version As JObject In MinecraftVersions + If Version("id").ToString <> VersionWaitingSelect Then Continue For + Logger.Info($"自动选择 MC 版本:{VersionWaitingSelect}") + MinecraftSelected(McDownloadListItem(Version, Nothing, False).Init(), Nothing) + VersionWaitingSelect = Nothing + Return + Next + End Sub + ''' ''' 当 MC 版本列表加载完时,立即自动选择的版本。用于外部调用。 ''' @@ -550,16 +560,16 @@ Public Class PageDownloadInstall ''' ''' 获取 OptiFine 的加载异常信息。若正常则返回 Nothing。 ''' - Private Function LoadOptiFineGetError() As String + Public Function LoadOptiFineGetError() As String If SelectedNeoForge IsNot Nothing Then Return "与 NeoForge 不兼容" '检查 Forge 1.13 - 1.14.3:全部不兼容 If SelectedForge IsNot Nothing AndAlso - CompareVersion(VanillaName, "1.13") >= 0 AndAlso CompareVersion("1.14.3", VanillaName) >= 0 Then + McUtils.CompareVersionGE(VanillaName, "1.13") AndAlso McUtils.CompareVersionGE("1.14.3", VanillaName) Then Return "与 Forge 不兼容" End If '检查 Fabric 1.20.5+:全部不兼容 If SelectedFabric IsNot Nothing AndAlso - CompareVersion(VanillaName, "1.20.4") > 0 Then + McUtils.CompareVersion(VanillaName, "1.20.4") > 0 Then Return "与 Fabric 不兼容" End If '检查 Loader @@ -584,12 +594,12 @@ Public Class PageDownloadInstall End Function '检查某个 OptiFine 是否与某个 Forge 兼容 - Private Function IsOptiFineSuitForForge(OptiFine As DlOptiFineListEntry, Forge As DlForgeVersionEntry) + Public Shared Function IsOptiFineSuitForForge(OptiFine As DlOptiFineListEntry, Forge As DlForgeVersionEntry) As Boolean If Forge.Inherit <> OptiFine.Inherit Then Return False '不是同一个大版本 If OptiFine.RequiredForgeVersion Is Nothing Then Return False '不兼容 Forge If String.IsNullOrWhiteSpace(OptiFine.RequiredForgeVersion) Then Return True '#4183 If OptiFine.RequiredForgeVersion.Contains(".") Then 'XX.X.XXX - Return CompareVersion(Forge.Version.ToString, OptiFine.RequiredForgeVersion) = 0 + Return McUtils.CompareVersion(Forge.Version.ToString, OptiFine.RequiredForgeVersion) = 0 Else 'XXXX Return Forge.Version.Revision = OptiFine.RequiredForgeVersion End If @@ -619,7 +629,7 @@ Public Class PageDownloadInstall Function(Left As DlOptiFineListEntry, Right As DlOptiFineListEntry) As Boolean If Not Left.IsPreview AndAlso Right.IsPreview Then Return True If Left.IsPreview AndAlso Not Right.IsPreview Then Return False - Return CompareVersionGE(Left.DisplayName, Right.DisplayName) + Return McUtils.CompareVersionGE(Left.DisplayName, Right.DisplayName) End Function) '可视化 PanOptiFine.Children.Clear() @@ -641,12 +651,12 @@ Public Class PageDownloadInstall CardOptiFine.IsSwapped = True ReloadSelected() End Sub - Private Sub OptiFine_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnOptiFineClear.MouseLeftButtonUp + Private Sub OptiFine_Clear(sender As Object, e As EventArgs) Handles BtnOptiFineClear.Click SelectedOptiFine = Nothing SelectedOptiFabric = Nothing + BtnOptiFabricClear.Visibility = Visibility.Collapsed AutoSelectedOptiFabric = False CardOptiFine.IsSwapped = True - e.Handled = True Forge_Loaded() NeoForge_Loaded() ReloadSelected() @@ -659,7 +669,7 @@ Public Class PageDownloadInstall ''' ''' 获取 LiteLoader 的加载异常信息。若正常则返回 Nothing。 ''' - Private Function LoadLiteLoaderGetError() As String + Public Function LoadLiteLoaderGetError() As String '检查 Loader If GetLoaderError(LoadLiteLoader) IsNot Nothing Then Return GetLoaderError(LoadLiteLoader) '检查版本 @@ -699,10 +709,9 @@ Public Class PageDownloadInstall CardLiteLoader.IsSwapped = True ReloadSelected() End Sub - Private Sub LiteLoader_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnLiteLoaderClear.MouseLeftButtonUp + Private Sub LiteLoader_Clear(sender As Object, e As EventArgs) Handles BtnLiteLoaderClear.Click SelectedLiteLoader = Nothing CardLiteLoader.IsSwapped = True - e.Handled = True ReloadSelected() End Sub @@ -713,8 +722,8 @@ Public Class PageDownloadInstall ''' ''' 获取 Forge 的加载异常信息。若正常则返回 Nothing。 ''' - Private Function LoadForgeGetError() As String - If CompareVersionGE("1.5.1", VanillaName) AndAlso CompareVersionGE(VanillaName, "1.1") Then Return "无" + Public Function LoadForgeGetError() As String + If McUtils.CompareVersionGE("1.5.1", VanillaName) AndAlso McUtils.CompareVersionGE(VanillaName, "1.1") Then Return "无" '检查 Loader If GetLoaderError(LoadForge) IsNot Nothing Then Return GetLoaderError(LoadForge) Dim Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry)) = LoadForge.State @@ -725,7 +734,7 @@ Public Class PageDownloadInstall If SelectedNeoForge IsNot Nothing Then Return "与 NeoForge 不兼容" If SelectedFabric IsNot Nothing Then Return "与 Fabric 不兼容" If SelectedOptiFine IsNot Nothing AndAlso - CompareVersionGE(VanillaName, "1.13") AndAlso CompareVersionGE("1.14.3", VanillaName) Then + McUtils.CompareVersionGE(VanillaName, "1.13") AndAlso McUtils.CompareVersionGE("1.14.3", VanillaName) Then Return "与 OptiFine 不兼容" '1.13 ~ 1.14.3 OptiFine 检查 End If If SelectedOptiFine IsNot Nothing AndAlso Not IsOptiFineSuitForForge(SelectedOptiFine, Version) Then Continue For @@ -775,10 +784,9 @@ Public Class PageDownloadInstall OptiFine_Loaded() ReloadSelected() End Sub - Private Sub Forge_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnForgeClear.MouseLeftButtonUp + Private Sub Forge_Clear(sender As Object, e As EventArgs) Handles BtnForgeClear.Click SelectedForge = Nothing CardForge.IsSwapped = True - e.Handled = True OptiFine_Loaded() ReloadSelected() End Sub @@ -790,7 +798,7 @@ Public Class PageDownloadInstall ''' ''' 获取 NeoForge 的加载异常信息。若正常则返回 Nothing。 ''' - Private Function LoadNeoForgeGetError() As String + Public Function LoadNeoForgeGetError() As String If SelectedOptiFine IsNot Nothing Then Return "与 OptiFine 不兼容" If SelectedForge IsNot Nothing Then Return "与 Forge 不兼容" If SelectedFabric IsNot Nothing Then Return "与 Fabric 不兼容" @@ -832,10 +840,9 @@ Public Class PageDownloadInstall OptiFine_Loaded() ReloadSelected() End Sub - Private Sub NeoForge_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnNeoForgeClear.MouseLeftButtonUp + Private Sub NeoForge_Clear(sender As Object, e As EventArgs) Handles BtnNeoForgeClear.Click SelectedNeoForge = Nothing CardNeoForge.IsSwapped = True - e.Handled = True OptiFine_Loaded() ReloadSelected() End Sub @@ -847,9 +854,9 @@ Public Class PageDownloadInstall ''' ''' 获取 Fabric 的加载异常信息。若正常则返回 Nothing。 ''' - Private Function LoadFabricGetError() As String + Public Function LoadFabricGetError() As String '检查 OptiFine 1.20.5+:没有 OptiFabric 故全部不兼容 - If SelectedOptiFine IsNot Nothing AndAlso CompareVersionGE(VanillaName, "1.20.5") Then Return "与 OptiFine 不兼容" + If SelectedOptiFine IsNot Nothing AndAlso McUtils.CompareVersionGE(VanillaName, "1.20.5") Then Return "与 OptiFine 不兼容" '检查 Loader If GetLoaderError(LoadFabric) IsNot Nothing Then Return GetLoaderError(LoadFabric) '检查版本 @@ -879,9 +886,10 @@ Public Class PageDownloadInstall If Not Versions.Any() Then Return '可视化 PanFabric.Children.Clear() - PanFabric.Tag = Versions CardFabric.SwapControl = PanFabric - CardFabric.SwapType = 12 + CardFabric.SetContentBuilder(Versions.OfType(Of JObject), + Function(Version) FabricDownloadListItem(Version, AddressOf Fabric_Selected)) + If Not CardFabric.IsSwapped Then CardFabric.BuildContent() Catch ex As Exception Logger.Error(ex, "可视化 Fabric 安装版本列表出错") End Try @@ -895,14 +903,15 @@ Public Class PageDownloadInstall CardFabric.IsSwapped = True ReloadSelected() End Sub - Private Sub Fabric_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnFabricClear.MouseLeftButtonUp + Private Sub Fabric_Clear(sender As Object, e As EventArgs) Handles BtnFabricClear.Click SelectedFabric = Nothing SelectedFabricApi = Nothing + BtnFabricApiClear.Visibility = Visibility.Collapsed AutoSelectedFabricApi = False SelectedOptiFabric = Nothing + BtnOptiFabricClear.Visibility = Visibility.Collapsed AutoSelectedOptiFabric = False CardFabric.IsSwapped = True - e.Handled = True ReloadSelected() End Sub @@ -911,14 +920,14 @@ Public Class PageDownloadInstall #Region "Fabric API 列表" ''' - ''' 判断某 Fabric API 是否适配当前选择的原版版本。 + ''' 判断某 Fabric API 是否适配指定的原版版本。 ''' - Public Function IsFabricApiCompatible(FabricApi As ResourceVersion) As Boolean + Public Shared Function IsFabricApiCompatible(FabricApi As ResourceVersion, MinecraftVersion As String) As Boolean Dim FabricApiName = FabricApi.Display Try - If FabricApiName Is Nothing OrElse VanillaName Is Nothing Then Return False + If FabricApiName Is Nothing OrElse MinecraftVersion Is Nothing Then Return False FabricApiName = FabricApiName.Lower - Dim TargetName = VanillaName.Replace("∞", "infinite").Replace("Combat Test 7c", "1.16_combat-3").Lower + Dim TargetName = MinecraftVersion.Replace("∞", "infinite").Replace("Combat Test 7c", "1.16_combat-3").Lower If FabricApiName.StartsWithF("[" & TargetName & "]") Then Return True If Not FabricApiName.Contains("/") OrElse Not FabricApiName.Contains("]") Then Return False '直接的判断(例如 1.18.1/22w03a) @@ -946,7 +955,7 @@ Public Class PageDownloadInstall End While Return True Catch ex As Exception - Logger.Warn(ex, $"判断 Fabric API 版本适配性出错({FabricApiName}, {VanillaName})") + Logger.Warn(ex, $"判断 Fabric API 版本适配性出错({FabricApiName}, {MinecraftVersion})") Return False End Try End Function @@ -954,12 +963,12 @@ Public Class PageDownloadInstall ''' ''' 获取 FabricApi 的加载异常信息。若正常则返回 Nothing。 ''' - Private Function LoadFabricApiGetError() As String + Public Function LoadFabricApiGetError() As String '检查 Loader If GetLoaderError(LoadFabricApi) IsNot Nothing Then Return GetLoaderError(LoadFabricApi) If DlFabricApiLoader.Output Is Nothing Then Return If(SelectedFabric Is Nothing, "需要安装 Fabric", "获取中……") '检查版本 - If DlFabricApiLoader.Output.Any(Function(f) IsFabricApiCompatible(f)) Then + If DlFabricApiLoader.Output.Any(Function(f) IsFabricApiCompatible(f, VanillaName)) Then Return If(SelectedFabric Is Nothing, "需要安装 Fabric", Nothing) Else Return "无" @@ -971,7 +980,7 @@ Public Class PageDownloadInstall If LoadFabricApiGetError() IsNot Nothing Then e.Handled = True End Sub - Private AutoSelectedFabricApi As Boolean = False + Public AutoSelectedFabricApi As Boolean = False ''' ''' 尝试重新可视化 FabricApi 版本列表。 ''' @@ -982,7 +991,7 @@ Public Class PageDownloadInstall '获取版本列表 Dim Versions As New List(Of ResourceVersion) For Each Version In DlFabricApiLoader.Output - If IsFabricApiCompatible(Version) Then + If IsFabricApiCompatible(Version, VanillaName) Then If Not Version.Display.StartsWithF("[") Then Logger.Warn($"已特判修改 Fabric API 显示名:{Version.Display}") Version.Display = "[" & VanillaName & "] " & Version.Display @@ -995,7 +1004,6 @@ Public Class PageDownloadInstall '可视化 PanFabricApi.Children.Clear() For Each Version In Versions - If Not IsFabricApiCompatible(Version) Then Continue For PanFabricApi.Children.Add(FabricApiDownloadListItem(Version, AddressOf FabricApi_Selected)) Next '自动选择 Fabric API @@ -1016,10 +1024,9 @@ Public Class PageDownloadInstall CardFabricApi.IsSwapped = True ReloadSelected() End Sub - Private Sub FabricApi_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnFabricApiClear.MouseLeftButtonUp + Private Sub FabricApi_Clear(sender As Object, e As EventArgs) Handles BtnFabricApiClear.Click SelectedFabricApi = Nothing CardFabricApi.IsSwapped = True - e.Handled = True ReloadSelected() End Sub @@ -1028,23 +1035,23 @@ Public Class PageDownloadInstall #Region "OptiFabric 列表" ''' - ''' 判断某 OptiFabric 是否适配当前选择的原版版本。 + ''' 判断某 OptiFabric 是否适配指定的原版版本。 ''' - Private Function IsOptiFabricCompatible(ModFile As ResourceVersion) As Boolean + Public Shared Function IsOptiFabricCompatible(ModFile As ResourceVersion, MinecraftVersion As String) As Boolean Try - If VanillaName Is Nothing Then Return False - Return ModFile.GameVersions.Contains(VanillaName) + If MinecraftVersion Is Nothing Then Return False + Return ModFile.GameVersions.Contains(MinecraftVersion) Catch ex As Exception - Logger.Warn(ex, $"判断 OptiFabric 版本适配性出错({VanillaName})") + Logger.Warn(ex, $"判断 OptiFabric 版本适配性出错({MinecraftVersion})") Return False End Try End Function - Private AutoSelectedOptiFabric As Boolean = False + Public AutoSelectedOptiFabric As Boolean = False ''' ''' 获取 OptiFabric 的加载异常信息。若正常则返回 Nothing。 ''' - Private Function LoadOptiFabricGetError() As String + Public Function LoadOptiFabricGetError() As String If VanillaDrop >= 140 AndAlso VanillaDrop <= 150 Then Return "不兼容老版本 Fabric,请手动下载 OptiFabric Origins" '检查 Loader If GetLoaderError(LoadOptiFabric) IsNot Nothing Then Return GetLoaderError(LoadOptiFabric) @@ -1056,7 +1063,7 @@ Public Class PageDownloadInstall Return "获取中……" End If For Each Version In DlOptiFabricLoader.Output - If Not IsOptiFabricCompatible(Version) Then Continue For '2135# + If Not IsOptiFabricCompatible(Version, VanillaName) Then Continue For '2135# If SelectedFabric Is Nothing AndAlso SelectedOptiFine Is Nothing Then Return "需要安装 OptiFine 与 Fabric" If SelectedFabric Is Nothing Then Return "需要安装 Fabric" If SelectedOptiFine Is Nothing Then Return "需要安装 OptiFine" @@ -1080,7 +1087,7 @@ Public Class PageDownloadInstall '获取版本列表 Dim Versions As New List(Of ResourceVersion) For Each Version In DlOptiFabricLoader.Output - If IsOptiFabricCompatible(Version) Then Versions.Add(Version) + If IsOptiFabricCompatible(Version, VanillaName) Then Versions.Add(Version) Next If Not Versions.Any() Then Return '排序 @@ -1088,7 +1095,6 @@ Public Class PageDownloadInstall '可视化 PanOptiFabric.Children.Clear() For Each Version In Versions - If Not IsOptiFabricCompatible(Version) Then Continue For PanOptiFabric.Children.Add(OptiFabricDownloadListItem(Version, AddressOf OptiFabric_Selected)) Next '自动选择 OptiFabric @@ -1108,10 +1114,9 @@ Public Class PageDownloadInstall CardOptiFabric.IsSwapped = True ReloadSelected() End Sub - Private Sub OptiFabric_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnOptiFabricClear.MouseLeftButtonUp + Private Sub OptiFabric_Clear(sender As Object, e As EventArgs) Handles BtnOptiFabricClear.Click SelectedOptiFabric = Nothing CardOptiFabric.IsSwapped = True - e.Handled = True ReloadSelected() End Sub @@ -1119,10 +1124,42 @@ Public Class PageDownloadInstall #Region "安装" + ''' + ''' 获取当前选择的组件信息。 + ''' + Public Function GetSelectedComponents() As McComponents + Return New McComponents( + minecraftVersion:=VanillaName, + optiFineEntry:=If(SelectedOptiFine Is Nothing, Nothing, JObject.FromObject(SelectedOptiFine)), + forgeVersion:=SelectedForge?.VersionName, + neoForgeVersion:=SelectedNeoForge?.VersionName, + fabricVersion:=SelectedFabric, + fabricApiVersion:=If(SelectedFabricApi?.Version, If(SelectedFabricApi?.Id, SelectedFabricApi?.Display)), + optiFabricVersion:=If(SelectedOptiFabric?.Version, If(SelectedOptiFabric?.Id, SelectedOptiFabric?.Display)), + liteLoaderVersion:=SelectedLiteLoader?.JsonToken?("version").ToString()) + End Function + Private Sub TextSelectName_KeyDown(sender As Object, e As KeyEventArgs) Handles TextSelectName.KeyDown If e.Key = Key.Enter AndAlso IsInSelectPage AndAlso BtnStart.IsEnabled Then BtnStart_Click() End Sub Private Sub BtnStart_Click() Handles BtnStart.Click + '提交安装申请 + Dim InstanceName As String = TextSelectName.Text + Dim Request As New McInstallRequest With { + .NewInstanceName = InstanceName, + .InstanceFolder = $"{McFolderSelected}versions\{InstanceName}\", + .Components = GetSelectedComponents(), + .MinecraftJson = VanillaData("url").ToString, + .ForgeEntry = SelectedForge, + .NeoForgeEntry = SelectedNeoForge, + .FabricApi = SelectedFabricApi, + .OptiFabric = SelectedOptiFabric, + .LiteLoaderEntry = SelectedLiteLoader + } + StartInstall(Request) + End Sub + + Public Overridable Sub StartInstall(Request As McInstallRequest) '确认版本隔离 If (SelectedForge IsNot Nothing OrElse SelectedNeoForge IsNot Nothing OrElse SelectedFabric IsNot Nothing) AndAlso (Settings.Get(Of Integer)("LaunchArgumentIndieV2") = 0 OrElse Settings.Get(Of Integer)("LaunchArgumentIndieV2") = 2) Then @@ -1132,22 +1169,7 @@ Public Class PageDownloadInstall Return End If End If - '提交安装申请 - Dim InstanceName As String = TextSelectName.Text - Dim Request As New McInstallRequest With { - .NewInstanceName = InstanceName, - .VersionFolder = $"{McFolderSelected}versions\{InstanceName}\", - .MinecraftJson = VanillaData("url").ToString, - .MinecraftName = VanillaName, - .OptiFineEntry = SelectedOptiFine, - .ForgeEntry = SelectedForge, - .NeoForgeEntry = SelectedNeoForge, - .FabricVersion = SelectedFabric, - .FabricApi = SelectedFabricApi, - .OptiFabric = SelectedOptiFabric, - .LiteLoaderEntry = SelectedLiteLoader - } - If Not McInstall(Request) Then Return + If Not McInstallStart(Request) Then Return '返回,这样在再次进入安装页面时这个版本就会显示文件夹已重复 ExitSelectPage() End Sub diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadModify.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadModify.vb new file mode 100644 index 0000000..ae44e04 --- /dev/null +++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadModify.vb @@ -0,0 +1,727 @@ +''' +''' 版本修改页:完整还原已安装组件后,复用安装页的选择交互。 +''' +Public Class PageDownloadModify + Inherits PageDownloadInstall + + Private InitLoader As LoaderTask(Of McInstance, Object) + Private ForgeVersionLoader As LoaderTask(Of String, List(Of DlForgeVersionEntry)) + Private InitialTarget As McComponents '进入时的组件快照 + ''' + ''' 原版的版本号是否可靠(不是通过 json 模糊分析)。 + ''' + Private IsVanillaVersionReliable As Boolean + Private OriginalName As String + Private CustomLogo As String + ''' + ''' 开始修改 MC 版本时,记录当前选择的组件,以在修改 MC 版本后尝试自动选中相同组件。 + ''' + Private ReadOnly PendingComponents As New Dictionary(Of MyLoading, String) + Private IsRestoringComponents As Boolean + + Public Overrides Sub LoaderInit() '父级 Handles Me.Initialized + ControlInit() + InitLoader = New LoaderTask(Of McInstance, Object)("初始化版本修改", AddressOf LoadInstance) + PageLoaderInit(LoadMinecraft, PanLoad, PanAllBack, Nothing, InitLoader, AddressOf LoadInstanceFinished, Function() PageInstanceLeft.Instance) + End Sub + Public Overrides Sub Init() '父级 Handles Me.Loaded + PanBack.ScrollToHome() + Static IsLoad As Boolean = False + If IsLoad Then PageLoaderRestart() + IsLoad = True + End Sub + Private Sub PageUnloaded() Handles Me.Unloaded + InitLoader.Cancel() + End Sub + + ''' + ''' 读取或初始化版本的安装清单。 + ''' + Private Sub LoadInstance(Task As LoaderTask(Of McInstance, Object)) + + '旧状态清理 + RunInUiWait( + Sub() + AniStop("FrmDownloadInstall SelectPageSwitch") + IsSelectNameEdited = False + ClearSelected() + PendingComponents.Clear() + InitialTarget = Nothing + IsInSelectPage = False + PanAllBack.IsHitTestVisible = False + End Sub) + Task.Input.Config.DiscardCache() + + '获取 Components + Dim Manifest = Configs.McInstallManifest.Get(Task.Input.Config) + If Manifest IsNot Nothing AndAlso Manifest.SchemaVersion > Versions.McInstallManifestVersion Then + Logger.Warn($"安装清单由更新版本的 PCL 创建(清单版本:{Manifest.SchemaVersion},当前版本:{Versions.McInstallManifestVersion})") + Throw New Exception($"更新 PCL 后才能修改它!点击【设置 → 其他 → 检查更新】以更新。") + End If + If Manifest IsNot Nothing Then + Dim LatestManifest = Manifest.RefreshModComponents(Task.Input.PathIndie) + If LatestManifest IsNot Manifest Then + Manifest = LatestManifest + Configs.McInstallManifest.Set(Manifest, Task.Input.Config) + Task.Input.Config.Save() + End If + End If + Dim Components As McComponents + Dim OptiFineVersion As String = Nothing + If Manifest IsNot Nothing Then + '有清单:从清单读取 + Components = Manifest.Components + If Components Is Nothing Then Throw New Exception("安装清单缺少组件信息,无法还原已安装组件。") + IsVanillaVersionReliable = True + Else + '无清单:通过版本 JSON 获取 + Dim VersionInfo = New McInstance(Task.Input.PathVersion).Version + If VersionInfo.VanillaName = "Unknown" Then Throw New Exception("无法确定 Minecraft 版本,无法修改") + Dim UnknownLoaders = VersionInfo.ModLoaderVersions. + Where(Function(Loader) String.IsNullOrWhiteSpace(Loader.Value) OrElse Loader.Value = "未知版本"). + Select(Function(Loader) Loader.Key.ToString()).ToList() + If UnknownLoaders.Any() Then Throw New Exception($"无法确定 {UnknownLoaders.Join("、")} 的版本,无法修改") + Components = New McComponents(VersionInfo.VanillaName, VersionInfo.ModLoaderVersions) + OptiFineVersion = VersionInfo.OptiFine + IsVanillaVersionReliable = VersionInfo.Reliable + End If + Dim MinecraftVersion = Components.MinecraftVersion + If String.IsNullOrWhiteSpace(MinecraftVersion) Then Throw New Exception("无法确定当前 Minecraft 版本,请检查版本文件") + '等待子列表加载器完成获取 + Dim LoaderList As New Dictionary(Of String, LoaderBase) From { + {"Minecraft", DlClientListLoader}, {"OptiFine", DlOptiFineListLoader}, {"LiteLoader", DlLiteLoaderListLoader}, + {"NeoForge", DlNeoForgeListLoader}, {"Fabric", DlFabricListLoader}, {"Fabric API", DlFabricApiLoader}, {"OptiFabric", DlOptiFabricLoader}} + Dim ForgeLoader As LoaderTask(Of String, List(Of DlForgeVersionEntry)) = Nothing + If McVersion.IsStandardFormat(MinecraftVersion) Then + ForgeLoader = New LoaderTask(Of String, List(Of DlForgeVersionEntry))("DlForgeVersion " & MinecraftVersion, AddressOf DlForgeVersionMain) + LoaderList.Add("Forge", ForgeLoader) + End If + For Each Loader In LoaderList.Values + Loader.Start(If(Loader Is ForgeLoader, MinecraftVersion, Nothing), Task.IsForceRestarting) + Next + Do + If Task.IsCanceled Then Return + For Each Pair In LoaderList + Dim Loader = Pair.Value + If Loader.State = LoadState.Failed Then + If Loader Is ForgeLoader AndAlso Loader.Error.Message = "无" Then Continue For 'Forge 无版本时不报错 + Throw New Exception($"获取 {Pair.Key} 版本列表失败,请稍后再试,或使用 VPN 改善网络环境", Loader.Error) + ElseIf Loader.State = LoadState.Canceled Then + Throw New Exception($"获取 {Pair.Key} 版本列表被中断,请稍后再试,或使用 VPN 改善网络环境") + End If + Next + If LoaderList.Values.All(Function(Loader) Loader.State = LoadState.Finished OrElse Loader.State = LoadState.Failed) Then Exit Do + Task.Progress = 0.7 * LoaderList.Values.Average(Function(Loader) Loader.Progress) + Thread.Sleep(10) + Loop + If Task.IsCanceled Then Return + Task.Progress = 0.7 + 'Minecraft + MinecraftVersions = CType(DlClientListLoader.Output.Value("versions").DeepClone(), JArray) + VanillaData = MinecraftVersions.OfType(Of JObject).FirstOrDefault( + Function(v) v("id").ToString.Replace("∞", "infinite") = MinecraftVersion.Replace("∞", "infinite")) + If VanillaData Is Nothing Then Throw New Exception($"Minecraft 列表中没有已安装的 {MinecraftVersion}") + VanillaData("id") = MinecraftVersion + 'OptiFine + If Manifest IsNot Nothing Then + SelectedOptiFine = Components.OptiFineEntry?.ToObject(Of DlOptiFineListEntry)() + If SelectedOptiFine IsNot Nothing AndAlso Not DlOptiFineListLoader.Output.Value.Any(Function(v) v.FileName = SelectedOptiFine.FileName) Then + '如果 OptiFine 是 pre 版,可能只在 BMCLAPI 下有,官方源删了。如果是这种缺失数据的情况,换用 BMCLAPI 源作为输出 + If DlOptiFineListBmclapiLoader.State <> LoadState.Finished Then DlOptiFineListBmclapiLoader.WaitForExit() + DlOptiFineListLoader.Output = DlOptiFineListBmclapiLoader.Output + End If + ElseIf OptiFineVersion IsNot Nothing AndAlso OptiFineVersion <> "未知版本" Then + SelectedOptiFine = DlOptiFineListLoader.Output.Value.FirstOrDefault( + Function(v) v.Inherit = MinecraftVersion AndAlso + v.InstanceName.AfterLast("-OptiFine_").Replace("HD_U_", "") = OptiFineVersion.Replace("HD_U_", "")) + If SelectedOptiFine Is Nothing Then Throw New Exception($"OptiFine 列表中没有已安装的 {OptiFineVersion}") + End If + 'Forge + If Components.ModLoaderVersions.ContainsKey(ModLoaders.Forge) Then + SelectedForge = ForgeLoader?.Output?.FirstOrDefault( + Function(v) v.Inherit = MinecraftVersion AndAlso v.VersionName = Components.ModLoaderVersions(ModLoaders.Forge) AndAlso + v.Category <> "universal" AndAlso v.Category <> "client" AndAlso (SelectedOptiFine Is Nothing OrElse IsOptiFineSuitForForge(SelectedOptiFine, v))) + If SelectedForge Is Nothing Then Throw New Exception($"Forge 列表中没有已安装的 {Components.ModLoaderVersions(ModLoaders.Forge)}") + End If + 'NeoForge + If Components.ModLoaderVersions.ContainsKey(ModLoaders.NeoForge) Then + SelectedNeoForge = DlNeoForgeListLoader.Output.Value.FirstOrDefault( + Function(v) v.Inherit = MinecraftVersion AndAlso v.VersionName = Components.ModLoaderVersions(ModLoaders.NeoForge)) + If SelectedNeoForge Is Nothing Then Throw New Exception($"NeoForge 列表中没有已安装的 {Components.ModLoaderVersions(ModLoaders.NeoForge)}") + End If + 'Fabric + If Components.ModLoaderVersions.ContainsKey(ModLoaders.Fabric) Then + Dim FabricEntry = DlFabricListLoader.Output.Value("loader").OfType(Of JObject).FirstOrDefault( + Function(v) v("version").ToString = Components.ModLoaderVersions(ModLoaders.Fabric), + Function(v) v("version").ToString.Replace("+build", "") = Components.ModLoaderVersions(ModLoaders.Fabric)) + If FabricEntry Is Nothing OrElse Not DlFabricListLoader.Output.Value("game").OfType(Of JObject). + Any(Function(v) v("version").ToString = MinecraftVersion.Replace("∞", "infinite").Replace("Combat Test 7c", "1.16_combat-3")) Then + Throw New Exception($"Fabric 列表中没有已安装的 {Components.ModLoaderVersions(ModLoaders.Fabric)}") + End If + SelectedFabric = FabricEntry("version").ToString + End If + 'LiteLoader + If Components.ModLoaderVersions.ContainsKey(ModLoaders.LiteLoader) Then + SelectedLiteLoader = DlLiteLoaderListLoader.Output.Value.FirstOrDefault( + Function(v) v.Inherit = MinecraftVersion AndAlso v.JsonToken("version").ToString = Components.ModLoaderVersions(ModLoaders.LiteLoader)) + If SelectedLiteLoader Is Nothing Then Throw New Exception($"LiteLoader 列表中没有已安装的 {Components.ModLoaderVersions(ModLoaders.LiteLoader)}") + End If + 'Fabric API + If Components.FabricApiVersion IsNot Nothing Then + SelectedFabricApi = DlFabricApiLoader.Output.FirstOrDefault( + Function(v) (v.Version = Components.FabricApiVersion OrElse v.Id = Components.FabricApiVersion OrElse + v.Display = Components.FabricApiVersion) AndAlso IsFabricApiCompatible(v, MinecraftVersion)) + If SelectedFabricApi Is Nothing Then Throw New Exception($"Fabric API 列表中没有已安装的 {Components.FabricApiVersion}") + End If + 'OptiFabric + If Components.OptiFabricVersion IsNot Nothing Then + SelectedOptiFabric = DlOptiFabricLoader.Output.FirstOrDefault( + Function(v) (v.Version = Components.OptiFabricVersion OrElse v.Id = Components.OptiFabricVersion OrElse + v.Display = Components.OptiFabricVersion) AndAlso IsOptiFabricCompatible(v, MinecraftVersion)) + If SelectedOptiFabric Is Nothing Then Throw New Exception($"OptiFabric 列表中没有已安装的 {Components.OptiFabricVersion}") + End If + + '扫描尚未识别的 Mod 组件 + Dim ManagedFiles As New List(Of McInstallManifest.ManagedFile) + If Manifest IsNot Nothing Then ManagedFiles.AddRange(Manifest.ManagedFiles) + +#Region "从 mods 文件夹中获取以 Mod 形式安装的组件信息" + + '获取 mods 文件夹下的 jar 文件与 SHA1 + Dim Mods As New Lazy(Of List(Of (File As FileInfo, Sha1 As String)))( + Function() + Dim ModsFolder = Task.Input.PathIndie & "mods\" + Dim Files = DirectoryUtils.EnumerateFiles(ModsFolder, searchPattern:="*.jar").ToList() + If SelectedLiteLoader IsNot Nothing Then 'LiteLoader 的 OptiFine 安装目录 + Files.AddRange(DirectoryUtils.EnumerateFiles(Path.Combine(ModsFolder, MinecraftVersion), searchPattern:="*.jar")) + End If + Dim Entries As New List(Of (File As FileInfo, Sha1 As String)) + For Each FilePath In Files + If Task.IsCanceled Then Return Entries + Dim File = FileUtils.GetInfo(FilePath) + Entries.Add((File, New LocalResourceFile(File).ModrinthHash)) + Task.Progress = 0.7 + 0.28 * Entries.Count / Files.Count + Next + Return Entries + End Function) + 'Fabric API + If SelectedFabric IsNot Nothing AndAlso SelectedFabricApi Is Nothing Then + Dim FabricApiList = DlFabricApiLoader.Output.Where(Function(v) IsFabricApiCompatible(v, MinecraftVersion)).ToList() + Dim Matches As New List(Of (File As FileInfo, Sha1 As String, Version As ResourceVersion)) + For Each Entry In Mods.Value + Dim Match = FabricApiList.FirstOrDefault(Function(v) v.Hash = Entry.Sha1) + If Match Is Nothing Then Continue For + Matches.Add((Entry.File, Entry.Sha1, Match)) + Next + Dim SelectedFile = SelectMod("Fabric API", Matches) + If SelectedFile IsNot Nothing Then + SelectedFabricApi = SelectedFile.Value.Version + ManagedFiles.Add(New McInstallManifest.ManagedFile("mods/" & SelectedFile.Value.File.Name.Lower(), SelectedFile.Value.Sha1, SelectedFile.Value.File.LastWriteTimeUtc, "FabricApi")) + End If + End If + If Task.IsCanceled Then Return + 'OptiFabric + If SelectedFabric IsNot Nothing AndAlso SelectedOptiFabric Is Nothing Then + Dim OptiFabricList = DlOptiFabricLoader.Output.Where(Function(v) IsOptiFabricCompatible(v, MinecraftVersion)).ToList() + Dim HasMd5 = OptiFabricList.Any(Function(v) If(v.Hash, "").Length = 32) 'CurseForge 的 OptiFabric 信息可能提供 SHA1 或 MD5 + Dim Matches As New List(Of (File As FileInfo, Sha1 As String, Version As ResourceVersion)) + For Each Entry In Mods.Value + Dim Match = OptiFabricList.FirstOrDefault(Function(v) v.Hash = Entry.Sha1) + If Match Is Nothing AndAlso HasMd5 Then + Dim Md5 = CryptographyUtils.ComputeFileHash(Entry.File.FullName, CryptographyUtils.HashMethod.Md5) + Match = OptiFabricList.FirstOrDefault(Function(v) v.Hash = Md5) + End If + If Match Is Nothing Then Continue For + Matches.Add((Entry.File, Entry.Sha1, Match)) + Next + Dim SelectedFile = SelectMod("OptiFabric", Matches) + If SelectedFile IsNot Nothing Then + SelectedOptiFabric = SelectedFile.Value.Version + ManagedFiles.Add(New McInstallManifest.ManagedFile("mods/" & SelectedFile.Value.File.Name.Lower(), SelectedFile.Value.Sha1, SelectedFile.Value.File.LastWriteTimeUtc, "OptiFabric")) + End If + End If + If Task.IsCanceled Then Return + 'OptiFine + Dim OptiFineFiles As New List(Of (File As FileInfo, Sha1 As String, OriginalName As String)) + If SelectedOptiFine Is Nothing AndAlso (SelectedForge IsNot Nothing OrElse SelectedLiteLoader IsNot Nothing OrElse SelectedFabric IsNot Nothing) Then 'OptiFine 只在有 Mod 加载器时才看在不在 mods 文件夹中 + Static OptiFineHashes As JObject = FileUtils.ReadAsJson("Minecraft\OptiFineHashes.json", type:=GetType(Resource)) 'JSON 内容:{"SHA1": "原始文件名", ...} + OptiFineFiles = Mods.Value. + Select(Function(Entry) (Entry.File, Entry.Sha1, OriginalName:=OptiFineHashes(Entry.Sha1)?.ToString())). '用本地文件的 SHA1 匹配下载时的原始文件名,再根据原始文件名取得下载源信息 + Where(Function(Entry) Entry.OriginalName IsNot Nothing).ToList() + Dim Matches As New List(Of (File As FileInfo, Sha1 As String, Version As DlOptiFineListEntry)) + For Each Entry In OptiFineFiles '遍历 hash 匹配到了的本地文件 + Dim Match = DlOptiFineListLoader.Output.Value.FirstOrDefault( + Function(v) v.Inherit = MinecraftVersion AndAlso v.FileName = Entry.OriginalName AndAlso (SelectedForge Is Nothing OrElse IsOptiFineSuitForForge(v, SelectedForge))) + If Match Is Nothing Then + Hint($"无法从 OptiFine 列表还原 {Entry.File.Name} 对应的 {Entry.OriginalName},将跳过该文件。") + Continue For + End If + Matches.Add((Entry.File, Entry.Sha1, Match)) + Next + Dim SelectedFile = SelectMod("OptiFine", Matches) + If SelectedFile IsNot Nothing Then + SelectedOptiFine = SelectedFile.Value.Version + Dim RelativePath = PathUtils.RemoveExtendedPrefix(SelectedFile.Value.File.FullName).Substring(Task.Input.PathIndie.Length).Replace("\", "/").Lower() + ManagedFiles.Add(New McInstallManifest.ManagedFile(RelativePath, SelectedFile.Value.Sha1, SelectedFile.Value.File.LastWriteTimeUtc, "OptiFine")) + End If + End If + If Task.IsCanceled Then Return + +#End Region + + '保存 Manifest + If IsVanillaVersionReliable Then + Components = New McComponents(MinecraftVersion, Components.ModLoaderVersions, + If(SelectedOptiFine Is Nothing, Nothing, JObject.FromObject(SelectedOptiFine)), + If(SelectedFabricApi?.Version, If(SelectedFabricApi?.Id, SelectedFabricApi?.Display)), + If(SelectedOptiFabric?.Version, If(SelectedOptiFabric?.Id, SelectedOptiFabric?.Display))) + If Manifest Is Nothing OrElse Not Components.IsEquivalentTo(Manifest.Components) OrElse Not ManagedFiles.SequenceEqual(Manifest.ManagedFiles) Then + Configs.McInstallManifest.Set(New McInstallManifest(Versions.McInstallManifestVersion, Components, Date.UtcNow, ManagedFiles), Task.Input.Config) + Task.Input.Config.Save() + Logger.Info($"已为 {Task.Input.Name} 更新安装清单") + End If + End If + If Task.IsCanceled Then Return + ForgeVersionLoader = ForgeLoader + End Sub + Private Function SelectMod(Of T)(ComponentName As String, Matches As List(Of (File As FileInfo, Sha1 As String, Version As T))) As (File As FileInfo, Sha1 As String, Version As T)? + If Matches.Count = 0 Then Return Nothing + If Matches.Count = 1 Then Return Matches(0) + Dim Choice As Integer? = Nothing + RunInUiWait(Sub() Choice = MyMsgBoxSelect(Matches.Select(Function(Entry) CType(New MyRadioBox With {.Text = Entry.File.Name}, IMyRadio)), $"选择当前使用的 {ComponentName}")) + If Not Choice.HasValue Then Throw New Exception($"发现了多个 {ComponentName}") + Return Matches(Choice.Value) + End Function + + ''' + ''' 初始化修改页 + ''' + Private Sub LoadInstanceFinished(Task As LoaderTask(Of McInstance, Object)) + '还原原版信息 + OriginalName = Task.Input.Name + CustomLogo = Nothing + If ReadIni(Task.Input.PathVersion & "PCL\Setup.ini", "LogoCustom", False) Then + CustomLogo = If(Task.Input.Logo.EndsWithF("PCL\Logo.png"), Task.Input.PathVersion & "PCL\Logo.png", Task.Input.Logo) + End If + Dim Item = McDownloadListItem(VanillaData, Nothing, False).Init() + VanillaName = Item.Title + VanillaIcon = Item.Logo + LabMinecraftVersion.Text = VanillaName + TextSelectName.Text = OriginalName + IsSelectNameEdited = Not String.Equals(OriginalName, GetSelectName(), StringComparison.Ordinal) + TextSelectName.ValidateRules = New ObjectModel.Collection(Of Validate) From { + New ValidateFolderName(McFolderSelected & "versions", IgnoreList:=New List(Of String) From {OriginalName})} + '修改页 UI + CardMinecraft.Visibility = Visibility.Visible + BtnBack.Visibility = Visibility.Collapsed + ImgLogo.Margin = New Thickness(15, 15, 0, 0) + TextSelectName.Margin = New Thickness(56, 16, 16, 0) + LabSelectName.Text = $"原名 :   {OriginalName}" + UpdateOriginalNameVisibility() + BtnStart.Text = "开始修改" + BtnStart.Logo = FrmInstanceLeft.ItemModify.Logo + AutoSelectedFabricApi = SelectedFabric IsNot Nothing + AutoSelectedOptiFabric = SelectedFabric IsNot Nothing AndAlso SelectedOptiFine IsNot Nothing + If ForgeVersionLoader IsNot Nothing Then LoadForge.State = ForgeVersionLoader + PrepareSelectPage() + ReloadComponentLists() + '进入动画 + PanMinecraft.Visibility = Visibility.Collapsed + PanMinecraft.Opacity = 0 + PanMinecraft.RenderTransform = New TranslateTransform(-50, 0) + PanSelect.Opacity = 1 + PanSelect.RenderTransform = New TranslateTransform + PanBack.IsHitTestVisible = True + PanAllBack.IsHitTestVisible = True + InitialTarget = GetSelectedComponents() + '设置原始数据 + BtnMinecraftRestore.Tag = VanillaData + BtnOptiFineRestore.Tag = SelectedOptiFine + BtnForgeRestore.Tag = SelectedForge + BtnNeoForgeRestore.Tag = SelectedNeoForge + BtnFabricRestore.Tag = SelectedFabric + BtnFabricApiRestore.Tag = SelectedFabricApi + BtnOptiFabricRestore.Tag = SelectedOptiFabric + BtnLiteLoaderRestore.Tag = SelectedLiteLoader + ImgMinecraftOld.Source = VanillaIcon + LabMinecraftOld.Text = LabMinecraftVersion.Text + LabOptiFineOld.Text = If(SelectedOptiFine Is Nothing, "无", LabOptiFine.Text) + LabForgeOld.Text = If(SelectedForge Is Nothing, "无", LabForge.Text) + LabNeoForgeOld.Text = If(SelectedNeoForge Is Nothing, "无", LabNeoForge.Text) + LabFabricOld.Text = If(SelectedFabric Is Nothing, "无", LabFabric.Text) + LabLiteLoaderOld.Text = If(SelectedLiteLoader Is Nothing, "无", LabLiteLoader.Text) + LabFabricApiOld.Text = If(SelectedFabricApi Is Nothing, "无", LabFabricApi.Text) + LabOptiFabricOld.Text = If(SelectedOptiFabric Is Nothing, "无", LabOptiFabric.Text) + RefreshComparisons() + End Sub + + Public Overrides Function GetSelectLogo() As String + Return If(CustomLogo, MyBase.GetSelectLogo()) + End Function + + Public Overrides Sub ShowMinecraftVersionList() + If IsInSelectPage Then + '把当前已选择的组件记录到 PendingComponents + Dim Components = GetSelectedComponents() + For Each Component In { + (Loader:=LoadOptiFine, Version:=Components.OptiFineVersion), + (Loader:=LoadFabricApi, Version:=Components.FabricApiVersion), + (Loader:=LoadOptiFabric, Version:=Components.OptiFabricVersion), + (Loader:=LoadForge, Version:=SelectedForge?.VersionName), + (Loader:=LoadNeoForge, Version:=SelectedNeoForge?.VersionName), + (Loader:=LoadFabric, Version:=SelectedFabric), + (Loader:=LoadLiteLoader, Version:=SelectedLiteLoader?.JsonToken?("version").ToString())} + '保留尚未加载完成的组件选择 + If Component.Version IsNot Nothing Then PendingComponents(Component.Loader) = Component.Version + Next + End If + MyBase.ShowMinecraftVersionList() + End Sub + + Public Overrides Sub ReloadSelected() + If IsRestoringComponents Then Return + If IsInSelectPage AndAlso VanillaName IsNot Nothing AndAlso PendingComponents.Any() Then + IsRestoringComponents = True + Try + Dim HasProcessed As Boolean = False + '优先恢复 Mod 加载器,再匹配兼容的附加组件 + For Each LoadingComponent In {LoadFabric, LoadForge, LoadNeoForge, LoadLiteLoader, LoadOptiFine, LoadFabricApi, LoadOptiFabric} + Dim Version As String = Nothing + If Not PendingComponents.TryGetValue(LoadingComponent, Version) Then Continue For + If Not LoadingComponent.State.IsLoader Then Continue For + Dim Loader = DirectCast(LoadingComponent.State, LoaderBase) + '检查前置组件 + If LoadingComponent Is LoadOptiFine Then + If {LoadFabric, LoadForge, LoadNeoForge, LoadLiteLoader}.Any(Function(Component) PendingComponents.ContainsKey(Component)) Then Continue For + ElseIf LoadingComponent Is LoadFabricApi Then + If PendingComponents.ContainsKey(LoadFabric) Then Continue For + ElseIf LoadingComponent Is LoadForge Then + If Not McVersion.IsStandardFormat(VanillaName) Then + PendingComponents.Remove(LoadingComponent) + Continue For + End If + If DirectCast(Loader, LoaderTask(Of String, List(Of DlForgeVersionEntry))).Input <> VanillaName Then Continue For + If Loader.State = LoadState.Failed AndAlso Loader.Error.Message = "无" Then + PendingComponents.Remove(LoadingComponent) + Continue For + End If + ElseIf LoadingComponent Is LoadOptiFabric Then + If PendingComponents.ContainsKey(LoadFabric) OrElse PendingComponents.ContainsKey(LoadOptiFine) Then Continue For + End If + If Loader.State <> LoadState.Finished Then Continue For + '移除已处理的组件 + PendingComponents.Remove(LoadingComponent) + HasProcessed = True + '匹配目标 MC 版本下兼容的组件 + If LoadingComponent Is LoadFabric Then + If SelectedFabric Is Nothing AndAlso LoadFabricGetError() Is Nothing Then + Dim Versions = DlFabricListLoader.Output.Value("loader").OfType(Of JObject) + SelectedFabric = If(Versions.FirstOrDefault(Function(v) v("version").ToString = Version), Versions.FirstOrDefault())?("version").ToString() + End If + ElseIf LoadingComponent Is LoadOptiFine Then + If SelectedOptiFine Is Nothing AndAlso LoadOptiFineGetError() Is Nothing Then + Dim Versions = DlOptiFineListLoader.Output.Value.Where(Function(v) v.Inherit = VanillaName AndAlso + (SelectedForge Is Nothing OrElse IsOptiFineSuitForForge(v, SelectedForge))) + SelectedOptiFine = If(Versions.FirstOrDefault(Function(v) v.InstanceName.AfterLast("-OptiFine_") = Version), + Versions.OrderBy(Function(v) v.IsPreview). + ThenByDescending(Function(v) v.DisplayName, Comparer(Of String).Create(AddressOf McUtils.CompareVersion)).FirstOrDefault()) + End If + ElseIf LoadingComponent Is LoadForge Then + If SelectedForge Is Nothing AndAlso LoadForgeGetError() Is Nothing Then + Dim Versions = DirectCast(Loader, LoaderTask(Of String, List(Of DlForgeVersionEntry))).Output.Where( + Function(v) v.Inherit = VanillaName AndAlso + v.Category <> "universal" AndAlso v.Category <> "client" AndAlso + (SelectedOptiFine Is Nothing OrElse IsOptiFineSuitForForge(SelectedOptiFine, v))) + SelectedForge = If(Versions.FirstOrDefault(Function(v) v.VersionName = Version), Versions.OrderByDescending(Function(v) v).FirstOrDefault()) + End If + ElseIf LoadingComponent Is LoadNeoForge Then + If SelectedNeoForge Is Nothing AndAlso LoadNeoForgeGetError() Is Nothing Then + Dim Versions = DlNeoForgeListLoader.Output.Value.Where(Function(v) v.Inherit = VanillaName) + SelectedNeoForge = If(Versions.FirstOrDefault(Function(v) v.VersionName = Version), Versions.FirstOrDefault()) + End If + ElseIf LoadingComponent Is LoadLiteLoader Then + If SelectedLiteLoader Is Nothing AndAlso LoadLiteLoaderGetError() Is Nothing Then + Dim Versions = DlLiteLoaderListLoader.Output.Value.Where(Function(v) v.Inherit = VanillaName) + SelectedLiteLoader = If(Versions.FirstOrDefault(Function(v) v.JsonToken("version").ToString = Version), Versions.FirstOrDefault()) + End If + ElseIf LoadingComponent Is LoadFabricApi Then + If SelectedFabricApi Is Nothing AndAlso LoadFabricApiGetError() Is Nothing Then + Dim Versions = DlFabricApiLoader.Output.Where(Function(v) IsFabricApiCompatible(v, VanillaName)) + SelectedFabricApi = If(Versions.FirstOrDefault(Function(v) v.Version = Version OrElse v.Id = Version OrElse v.Display = Version), + Versions.OrderByDescending(Function(v) v.ReleaseDate).FirstOrDefault()) + End If + If SelectedFabricApi IsNot Nothing Then AutoSelectedFabricApi = True + ElseIf LoadingComponent Is LoadOptiFabric Then + If SelectedOptiFabric Is Nothing AndAlso LoadOptiFabricGetError() Is Nothing Then + Dim Versions = DlOptiFabricLoader.Output.Where(Function(v) IsOptiFabricCompatible(v, VanillaName)) + SelectedOptiFabric = If(Versions.FirstOrDefault(Function(v) v.Version = Version OrElse v.Id = Version OrElse v.Display = Version), + Versions.OrderByDescending(Function(v) v.ReleaseDate).FirstOrDefault()) + End If + If SelectedOptiFabric IsNot Nothing Then AutoSelectedOptiFabric = True + End If + Next + '更新组件列表 + If HasProcessed Then ReloadComponentLists() + Finally + IsRestoringComponents = False + End Try + End If + '刷新当前选择与原始版本的对比 + MyBase.ReloadSelected() + If InitialTarget IsNot Nothing Then RefreshComparisons() + End Sub + + Private Sub RefreshComparisons() + Dim Current = GetSelectedComponents() + ImgMinecraftNew.Source = VanillaIcon + 'Minecraft + Dim IsMinecraftUnchanged = InitialTarget.MinecraftVersion = Current.MinecraftVersion + ShowComparison(PanMinecraftCompare, PanMinecraftInfo, InitialTarget.MinecraftVersion, Current.MinecraftVersion, + LabMinecraftVersion.Text, BtnMinecraftRestore, True) + 'OptiFine + Dim OriginalOptiFine = TryCast(BtnOptiFineRestore.Tag, DlOptiFineListEntry) + ShowComparison(PanOptiFineCompare, PanOptiFineInfo, InitialTarget.OptiFineVersion, Current.OptiFineVersion, + LabOptiFine.Text, BtnOptiFineRestore, InitialTarget.OptiFineEntry Is Nothing OrElse + IsMinecraftUnchanged AndAlso CardOptiFine.MainSwap.Visibility = Visibility.Visible AndAlso OriginalOptiFine IsNot Nothing AndAlso + (SelectedForge Is Nothing OrElse IsOptiFineSuitForForge(OriginalOptiFine, SelectedForge)), BtnOptiFineClear) + 'Fabric API + ShowComparison(PanFabricApiCompare, PanFabricApiInfo, InitialTarget.FabricApiVersion, Current.FabricApiVersion, + LabFabricApi.Text, BtnFabricApiRestore, InitialTarget.FabricApiVersion Is Nothing OrElse + IsMinecraftUnchanged AndAlso SelectedFabric IsNot Nothing AndAlso CardFabricApi.MainSwap.Visibility = Visibility.Visible, BtnFabricApiClear) + 'OptiFabric + ShowComparison(PanOptiFabricCompare, PanOptiFabricInfo, InitialTarget.OptiFabricVersion, Current.OptiFabricVersion, + LabOptiFabric.Text, BtnOptiFabricRestore, InitialTarget.OptiFabricVersion Is Nothing OrElse + IsMinecraftUnchanged AndAlso SelectedFabric IsNot Nothing AndAlso SelectedOptiFine IsNot Nothing AndAlso CardOptiFabric.MainSwap.Visibility = Visibility.Visible, BtnOptiFabricClear) + 'Mod Loader + For Each Loader In { + (Kind:=ModLoaders.Forge, Card:=CardForge, ComparePanel:=PanForgeCompare, CurrentPanel:=PanForgeInfo, Label:=LabForge, RestoreButton:=BtnForgeRestore, ClearButton:=BtnForgeClear), + (Kind:=ModLoaders.NeoForge, Card:=CardNeoForge, ComparePanel:=PanNeoForgeCompare, CurrentPanel:=PanNeoForgeInfo, Label:=LabNeoForge, RestoreButton:=BtnNeoForgeRestore, ClearButton:=BtnNeoForgeClear), + (Kind:=ModLoaders.Fabric, Card:=CardFabric, ComparePanel:=PanFabricCompare, CurrentPanel:=PanFabricInfo, Label:=LabFabric, RestoreButton:=BtnFabricRestore, ClearButton:=BtnFabricClear), + (Kind:=ModLoaders.LiteLoader, Card:=CardLiteLoader, ComparePanel:=PanLiteLoaderCompare, CurrentPanel:=PanLiteLoaderInfo, Label:=LabLiteLoader, RestoreButton:=BtnLiteLoaderRestore, ClearButton:=BtnLiteLoaderClear)} + Dim OldVersion As String = Nothing, NewVersion As String = Nothing + InitialTarget.ModLoaderVersions.TryGetValue(Loader.Kind, OldVersion) + Current.ModLoaderVersions.TryGetValue(Loader.Kind, NewVersion) + Dim CanRestore = OldVersion Is Nothing OrElse IsMinecraftUnchanged AndAlso + Loader.Card.MainSwap.Visibility = Visibility.Visible AndAlso Loader.RestoreButton.Tag IsNot Nothing + If Loader.Kind = ModLoaders.Forge AndAlso OldVersion IsNot Nothing AndAlso CanRestore Then + CanRestore = SelectedOptiFine Is Nothing OrElse IsOptiFineSuitForForge(SelectedOptiFine, DirectCast(Loader.RestoreButton.Tag, DlForgeVersionEntry)) + End If + If Loader.Kind = ModLoaders.Fabric AndAlso OldVersion IsNot Nothing Then + Dim TargetVersion = VanillaName.Replace("∞", "infinite").Replace("Combat Test 7c", "1.16_combat-3") + CanRestore = CardFabric.Visibility = Visibility.Visible AndAlso CardFabric.MainSwap.Visibility = Visibility.Visible AndAlso + DlFabricListLoader.Output.Value("loader").OfType(Of JObject).Any(Function(v) v("version").ToString = OldVersion) AndAlso + DlFabricListLoader.Output.Value("game").OfType(Of JObject).Any(Function(v) v("version").ToString = TargetVersion) + End If + ShowComparison(Loader.ComparePanel, Loader.CurrentPanel, OldVersion, NewVersion, Loader.Label.Text, + Loader.RestoreButton, CanRestore, Loader.ClearButton) + Next + UpdateStartButton() + End Sub + + Private Sub ShowComparison(ComparePanel As Grid, CurrentPanel As FrameworkElement, OldVersion As String, NewVersion As String, NewText As String, + RestoreButton As MyIconButton, CanRestore As Boolean, Optional ClearButton As FrameworkElement = Nothing) + '获取不可安装的原因 + Dim UnavailableReason As String = Nothing + If NewVersion Is Nothing Then + If RestoreButton Is BtnOptiFineRestore Then + UnavailableReason = LoadOptiFineGetError() + ElseIf RestoreButton Is BtnForgeRestore Then + UnavailableReason = If(McVersion.IsStandardFormat(VanillaName), LoadForgeGetError(), "无") + ElseIf RestoreButton Is BtnNeoForgeRestore Then + UnavailableReason = If(CardNeoForge.Visibility = Visibility.Collapsed, "无", LoadNeoForgeGetError()) + ElseIf RestoreButton Is BtnFabricRestore Then + UnavailableReason = If(CardFabric.Visibility = Visibility.Collapsed, "无", LoadFabricGetError()) + ElseIf RestoreButton Is BtnLiteLoaderRestore Then + UnavailableReason = If(CardLiteLoader.Visibility = Visibility.Collapsed, "无", LoadLiteLoaderGetError()) + ElseIf RestoreButton Is BtnFabricApiRestore Then + UnavailableReason = LoadFabricApiGetError() + ElseIf RestoreButton Is BtnOptiFabricRestore Then + UnavailableReason = LoadOptiFabricGetError() + End If + NewText = If(UnavailableReason, "可以添加") + End If + '修改对比 + Dim IsPending = NewVersion Is Nothing AndAlso (NewText = "获取中……" OrElse NewText.StartsWithF("获取失败:")) + Dim IsChanged = OldVersion <> NewVersion AndAlso Not IsPending + CanRestore = IsChanged AndAlso OldVersion IsNot Nothing AndAlso CanRestore + ComparePanel.Visibility = If(IsChanged, Visibility.Visible, Visibility.Collapsed) + CurrentPanel.Visibility = If(IsChanged, Visibility.Collapsed, Visibility.Visible) + '还原与取消安装按钮 + RestoreButton.Visibility = If(CanRestore, Visibility.Visible, Visibility.Collapsed) + If ClearButton IsNot Nothing Then ClearButton.ToolTip = If(OldVersion Is Nothing, "取消安装", "移除") + Dim HasClearButton = ClearButton IsNot Nothing AndAlso ClearButton.Visibility = Visibility.Visible + RestoreButton.Margin = New Thickness(0, 5, If(CanRestore AndAlso HasClearButton, 60, 32), 0) + ComparePanel.Margin = New Thickness(ComparePanel.Margin.Left, ComparePanel.Margin.Top, + If(CanRestore AndAlso HasClearButton, 110, 80), ComparePanel.Margin.Bottom) + If Not IsChanged Then Return + '显示修改前已安装组件的对比卡片 + If OldVersion IsNot Nothing Then + Dim Card = CType(ComparePanel.Parent, MyCard) + If Card.Visibility = Visibility.Collapsed Then + Card.IsSwapped = True + If Card.MainSwap IsNot Nothing Then Card.MainSwap.Visibility = Visibility.Collapsed + Card.Visibility = Visibility.Visible + End If + End If + Dim OldSide = CType(ComparePanel.Children(0), DockPanel) + Dim NewSide = CType(ComparePanel.Children(2), DockPanel) + OldSide.Children(0).Visibility = If(OldVersion Is Nothing, Visibility.Collapsed, Visibility.Visible) + CType(OldSide.Children(1), TextBlock).Margin = If(OldVersion Is Nothing, New Thickness(0), New Thickness(7, 0, 0, 0)) + NewSide.Children(0).Visibility = If(NewVersion Is Nothing, Visibility.Collapsed, Visibility.Visible) + Dim NewLabel = CType(NewSide.Children(1), TextBlock) + NewLabel.Margin = If(NewVersion Is Nothing, New Thickness(0), New Thickness(7, 0, 0, 0)) + NewLabel.Text = If(NewVersion Is Nothing, "移除", NewText) + If NewVersion Is Nothing AndAlso UnavailableReason IsNot Nothing Then + NewLabel.Inlines.Add(New Run($"({If(UnavailableReason = "无", "无可用版本", UnavailableReason)})") With {.Foreground = ColorGray4}) + End If + End Sub + + Private Sub RestoreComponent(sender As Object, e As EventArgs) Handles BtnMinecraftRestore.Click, BtnOptiFineRestore.Click, BtnForgeRestore.Click, + BtnNeoForgeRestore.Click, BtnFabricRestore.Click, BtnFabricApiRestore.Click, BtnOptiFabricRestore.Click, BtnLiteLoaderRestore.Click + Dim Button = DirectCast(sender, MyIconButton) + '恢复初始选择 + If Button Is BtnMinecraftRestore Then + IsRestoringComponents = True + Try + PendingComponents.Clear() + MinecraftSelected(McDownloadListItem(DirectCast(Button.Tag, JObject), Nothing, False).Init(), Nothing) + If ForgeVersionLoader IsNot Nothing Then LoadForge.State = ForgeVersionLoader + '恢复初始的组件 + SelectedOptiFine = TryCast(BtnOptiFineRestore.Tag, DlOptiFineListEntry) + SelectedForge = TryCast(BtnForgeRestore.Tag, DlForgeVersionEntry) + SelectedNeoForge = TryCast(BtnNeoForgeRestore.Tag, DlNeoForgeListEntry) + SelectedFabric = TryCast(BtnFabricRestore.Tag, String) + SelectedLiteLoader = TryCast(BtnLiteLoaderRestore.Tag, DlLiteLoaderListEntry) + SelectedFabricApi = TryCast(BtnFabricApiRestore.Tag, ResourceVersion) + SelectedOptiFabric = TryCast(BtnOptiFabricRestore.Tag, ResourceVersion) + AutoSelectedFabricApi = SelectedFabric IsNot Nothing + AutoSelectedOptiFabric = SelectedFabric IsNot Nothing AndAlso SelectedOptiFine IsNot Nothing + Finally + IsRestoringComponents = False + End Try + ReloadComponentLists() + Return + ElseIf Button Is BtnOptiFineRestore Then + SelectedOptiFine = TryCast(Button.Tag, DlOptiFineListEntry) + AutoSelectedOptiFabric = SelectedOptiFine IsNot Nothing AndAlso SelectedFabric IsNot Nothing + If SelectedOptiFine Is Nothing Then SelectedOptiFabric = Nothing + ElseIf Button Is BtnForgeRestore Then + SelectedForge = TryCast(Button.Tag, DlForgeVersionEntry) + ElseIf Button Is BtnNeoForgeRestore Then + SelectedNeoForge = TryCast(Button.Tag, DlNeoForgeListEntry) + ElseIf Button Is BtnFabricRestore Then + SelectedFabric = TryCast(Button.Tag, String) + AutoSelectedFabricApi = SelectedFabric IsNot Nothing + AutoSelectedOptiFabric = SelectedFabric IsNot Nothing AndAlso SelectedOptiFine IsNot Nothing + If SelectedFabric Is Nothing Then + SelectedFabricApi = Nothing + SelectedOptiFabric = Nothing + End If + ElseIf Button Is BtnFabricApiRestore Then + SelectedFabricApi = TryCast(Button.Tag, ResourceVersion) + AutoSelectedFabricApi = True + ElseIf Button Is BtnOptiFabricRestore Then + SelectedOptiFabric = TryCast(Button.Tag, ResourceVersion) + AutoSelectedOptiFabric = True + ElseIf Button Is BtnLiteLoaderRestore Then + SelectedLiteLoader = TryCast(Button.Tag, DlLiteLoaderListEntry) + End If + DirectCast(Button.Parent, MyCard).IsSwapped = True + '刷新组件 + ReloadComponentLists() + End Sub + + Private Sub CardMinecraft_MouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs) Handles CardMinecraft.MouseLeftButtonUp + ShowMinecraftVersionList() + e.Handled = True + End Sub + + Private Sub UpdateStartButton() Handles TextSelectName.TextChanged, TextSelectName.ValidateChanged + BtnStart.IsEnabled = TextSelectName.IsValidated AndAlso InitialTarget IsNot Nothing AndAlso + Not PendingComponents.Any() AndAlso + (Not IsVanillaVersionReliable OrElse Not InitialTarget.IsEquivalentTo(GetSelectedComponents()) OrElse + Not String.Equals(TextSelectName.Text, OriginalName, StringComparison.Ordinal)) + End Sub + + Private Sub UpdateOriginalNameVisibility() Handles TextSelectName.TextChanged + Dim IsNameChanged = Not String.Equals(TextSelectName.Text, OriginalName, StringComparison.Ordinal) + Dim NewVisibility = If(IsNameChanged, Visibility.Visible, Visibility.Collapsed) + If LabSelectName.Visibility = NewVisibility Then Return + LabSelectName.Visibility = NewVisibility + BtnOriginalNameRestore.Visibility = NewVisibility + Dim RightMargin = If(IsNameChanged, 40, 16) + If IsInSelectPage AndAlso AniControlEnabled = 0 Then + AniStart(AaDouble(Sub(Delta) TextSelectName.Margin = New Thickness(56, 16, TextSelectName.Margin.Right + Delta, 0), + RightMargin - TextSelectName.Margin.Right, 150,, New AniEaseOutFluent), "DownloadModify Name Margin") + Else + AniStop("DownloadModify Name Margin") + TextSelectName.Margin = New Thickness(56, 16, RightMargin, 0) + End If + End Sub + + Private Sub BtnOriginalNameRestore_Click(sender As Object, e As EventArgs) Handles BtnOriginalNameRestore.Click + TextSelectName.Text = OriginalName + IsSelectNameEdited = Not String.Equals(OriginalName, GetSelectName(), StringComparison.Ordinal) + End Sub + + Public Overrides Sub StartInstall(Request As McInstallRequest) + UpdateStartButton() + If Not BtnStart.IsEnabled Then Return + Dim TargetInstance = InitLoader.Input + Dim Target = Request.Components + Dim InstanceName = Request.NewInstanceName + Dim IsNameChanged = Not String.Equals(InstanceName, TargetInstance.Name, StringComparison.Ordinal) + Request.NewInstanceName = If(IsNameChanged, InstanceName, Nothing) + Request.InstanceFolder = TargetInstance.PathVersion + '警告 + Dim Warnings As New List(Of String) + If (SelectedForge IsNot Nothing OrElse SelectedNeoForge IsNot Nothing OrElse SelectedFabric IsNot Nothing) AndAlso + Not PathUtils.IsSame(TargetInstance.PathIndie, TargetInstance.PathVersion) Then + Warnings.Add("该版本尚未开启版本隔离,可能与别的 MC 版本共用同一个 Mod 和存档文件夹。" & vbCrLf & + "修改版本后,已有的 Mod、存档等需要手动迁移到新的游戏目录。") + End If + Dim IsMinecraftVersionChanged = InitialTarget.MinecraftVersion <> Target.MinecraftVersion + If Not IsVanillaVersionReliable AndAlso Not IsMinecraftVersionChanged Then + Warnings.Add($"PCL 未能精准识别该版本,请确认它是不是 Minecraft {InitialTarget.MinecraftVersion},如果不正确,请取消并选择正确的版本!") + End If + If Not Configs.HintModifyInstanceWarning.Get() Then + Warnings.Add("如果该版本安装了 PCL 不兼容的加载器,或经过第三方修改,这些改动将会丢失!") + End If + Dim IsModLoaderChanged As Boolean = False + For Each Loader In InitialTarget.ModLoaderVersions.Keys.Union(Target.ModLoaderVersions.Keys) + Dim CurrentVersion As String = Nothing, TargetVersion As String = Nothing + Dim HasCurrent = InitialTarget.ModLoaderVersions.TryGetValue(Loader, CurrentVersion) + Dim HasTarget = Target.ModLoaderVersions.TryGetValue(Loader, TargetVersion) + If HasCurrent <> HasTarget OrElse CurrentVersion <> TargetVersion Then + IsModLoaderChanged = True + Exit For + End If + Next + If (IsModLoaderChanged OrElse IsMinecraftVersionChanged) AndAlso InitialTarget.ModLoaderVersions.Any Then + Warnings.Add("现有的 Mod、资源包等在修改后可能会不兼容!") + End If + If Warnings.Any() Then + If MyMsgBox("请注意:" & vbCrLf & "• " & Warnings.Join(vbCrLf & "• "), "修改确认", "开始修改", "取消", IsWarn:=True) <> 1 Then Return + End If + If Not Configs.HintModifyInstanceWarning.Get() Then Configs.HintModifyInstanceWarning.Set(True) + '开始修改 + If IsVanillaVersionReliable AndAlso InitialTarget.IsEquivalentTo(Target) Then + '仅重命名 + Try + Dim OldPath = TargetInstance.PathVersion + Dim RenamedInstance = TargetInstance.Rename(InstanceName) + If PageInstanceLeft.Instance IsNot Nothing AndAlso PathUtils.IsSame(PageInstanceLeft.Instance.PathVersion, OldPath) Then PageInstanceLeft.Instance = RenamedInstance + If McInstanceSelected IsNot Nothing AndAlso PathUtils.IsSame(McInstanceSelected.PathVersion, OldPath) Then McInstanceSelected = RenamedInstance + PageInstanceLeft.ReloadCurrentJava() + Hint("重命名成功!", HintType.Green) + LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") + FrmMain.PageBack() + Catch ex As Exception + Logger.Error(ex, "重命名版本失败", LogBehavior.Alert) + End Try + Else + '实际修改 + If McModificationStart(Request, TargetInstance) Then FrmMain.PageBack() + End If + End Sub +End Class diff --git a/Plain Craft Launcher 2/Pages/PageDownload/Resource/PageDownloadCompDetail.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/Resource/PageDownloadCompDetail.xaml.vb index 7c624b0..e8ec615 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/Resource/PageDownloadCompDetail.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/Resource/PageDownloadCompDetail.xaml.vb @@ -64,10 +64,10 @@ Public Class PageDownloadResourceDetail If IsXSpecial Then Return 1 If IsYSpecial Then Return -1 '比较版本号 - Dim VersionCodeSort = -CompareVersion(x.Replace(x.BeforeFirst(" ") & " ", ""), y.Replace(y.BeforeFirst(" ") & " ", "")) + Dim VersionCodeSort = -McUtils.CompareVersion(x.Replace(x.BeforeFirst(" ") & " ", ""), y.Replace(y.BeforeFirst(" ") & " ", "")) If VersionCodeSort <> 0 Then Return VersionCodeSort '比较全部 - Return -CompareVersion(x, y) + Return -McUtils.CompareVersion(x, y) End Function Public Sub New(Optional Topmost As String = "") Me.Topmost = If(Topmost, "") @@ -98,7 +98,7 @@ Public Class PageDownloadResourceDetail Dim UpdateFilters = Sub() Filters = Results.SelectMany(Function(v) v.GameVersions).Select(Function(v) GetGroupedVersionName(v, GroupedDrop, GroupedOld)). - Distinct.OrderByDescending(Function(s) s, New VersionComparer).ToList + Distinct.OrderByDescending(Function(s) s, Comparer(Of String).Create(Function(x, y) McUtils.CompareVersion(x, y))).ToList End Sub '确定分组方式 @@ -174,7 +174,7 @@ GroupDone: Dim Loaders As New List(Of String) If Project.ModLoaders.Flags.Count > 1 AndAlso '工程至少有两个加载器 Version.ResourceType = ResourceTypes.Mod AndAlso '是 Mod - McVersion.IsFormatFit(VerName) Then '不是 “快照版本” 之类的 + McVersion.IsStandardFormat(VerName) Then '不是 “快照版本” 之类的 Loaders.AddRange(Version.ModLoaders.Flags.Select(Function(f) f.ToString & " ")) End If If Not Loaders.Any() Then Loaders.Add("") '保底加一个空的,确保它在一张卡片里 @@ -206,16 +206,17 @@ GroupDone: If Not Pair.Value.Any() Then Continue For If Pair.Key = TargetCardName.Replace("(所选版本)", "") Then Continue For '增加卡片 - Dim NewCard As New MyCard With {.Title = Pair.Key, .Margin = New Thickness(0, 0, 0, 15), .SwapType = If(TargetResourceType = ResourceTypes.ModPack, 9, 8)} '9 是安装,8 是另存为 + Dim NewCard As New MyCard With {.Title = Pair.Key, .Margin = New Thickness(0, 0, 0, 15)} Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = Pair.Value} NewCard.Children.Add(NewStack) NewCard.SwapControl = NewStack + NewCard.SetContentBuilder(AddressOf InitializeStackResourceVersions) PanResults.Children.Add(NewCard) '确定卡片是否默认展开 If Pair.Key = TargetCardName OrElse (FrmMain.PageCurrent.Additional IsNot Nothing AndAlso '#2761 CType(FrmMain.PageCurrent.Additional(1), List(Of String)).Contains(NewCard.Title)) Then - MyCard.StackInstall(NewStack, If(TargetResourceType = ResourceTypes.ModPack, 9, 8), Pair.Key) '9 是安装,8 是另存为 + NewCard.BuildContent() Else NewCard.IsSwapped = True End If @@ -240,13 +241,53 @@ GroupDone: Logger.Error(ex, "可视化工程下载列表出错") End Try End Sub + ''' + ''' 根据卡片中的 StackPanel.Tag 将资源版本列表数据实例化为 UI。 + ''' + Private Sub InitializeStackResourceVersions(Stack As StackPanel) + Dim Versions = CType(Stack.Tag, List(Of ResourceVersion)).SortByComparison(Function(a, b) a.ReleaseDate > b.ReleaseDate) + Dim HasDuplicates = Versions.DistinctBy(Function(a) a.Display).Count <> Versions.Count + Dim Dependences As List(Of String) + '获取并添加前置信息 + If TargetResourceType = ResourceTypes.ModPack Then + Dependences = New List(Of String) '整合包的前置信息会显示其中包含的一大堆 Mod,不是真正意义的前置 + Else + Dependences = Versions.SelectMany(Function(f) f.Dependencies).Distinct.Ordered.ToList() + End If + If Dependences.Any() Then + Dependences = Dependences.Where( + Function(d) + If Not ResourceProject.Cache.ContainsKey(d) Then Logger.Warn($"未找到 ID {d} 的前置信息") + Return ResourceProject.Cache.ContainsKey(d) + End Function).ToList + '添加开头间隔 + Stack.Children.Add(New TextBlock With {.Text = "前置资源", .FontSize = 14, .HorizontalAlignment = HorizontalAlignment.Left, .Margin = New Thickness(6, 2, 0, 5)}) + '添加前置列表 + For Each Dep In Dependences + Dim Item = ResourceProject.Cache(Dep).ToResourceItem(False, False) + Stack.Children.Add(Item) + Next + '添加结尾间隔 + Stack.Children.Add(New TextBlock With {.Text = "版本列表", .FontSize = 14, .HorizontalAlignment = HorizontalAlignment.Left, .Margin = New Thickness(6, 12, 0, 5)}) + End If + '实例化每个元素 + For Each Version In Versions + If TargetResourceType = ResourceTypes.ModPack Then + Stack.Children.Add(Version.ToListItem(AddressOf Install_Click, AddressOf Save_Click, IsBadDisplay:=HasDuplicates)) + Else + Stack.Children.Add(Version.ToListItem(AddressOf Save_Click, IsBadDisplay:=HasDuplicates)) + End If + Next + Stack.Children.Add(New FrameworkElement With {.Height = 18}) + Stack.Tag = Nothing + End Sub Private Function GetGroupedVersionName(Name As String, GroupedByDrop As Boolean, FoldOld As Boolean) As String Dim Drop = McVersion.VersionToDrop(Name) If Name Is Nothing Then Return "其他" ElseIf Name.Contains("w") OrElse Drop = 209 Then Return "快照版" - ElseIf Not McVersion.IsFormatFit(Name) OrElse (FoldOld AndAlso Drop < 120) Then + ElseIf Not McVersion.IsStandardFormat(Name) OrElse (FoldOld AndAlso Drop < 120) Then Return "远古版" ElseIf GroupedByDrop Then Return McVersion.DropToVersion(Drop) @@ -469,19 +510,23 @@ GroupDone: '弹窗要求选择保存位置 Dim Target As String Dim Ext As String = If(File.ResourceType = ResourceTypes.Mod, - If(File.FileName.EndsWithF(".litemod"), "litemod", "jar"), - If(File.FileName.EndsWithF(".mrpack"), "mrpack", "zip")) + If(PathUtils.GetExtension(File.FileName) = "litemod", "litemod", "jar"), + If(PathUtils.GetExtension(File.FileName) = "mrpack", "mrpack", "zip")) Target = Dialogs.SaveFile("选择保存位置", FileName, DefaultFolder, {(Ext, Desc & "文件")}) If Target Is Nothing Then Return '构造步骤加载器 Dim LoaderName As String = Desc & "下载:" & PathUtils.GetFileNameWithoutExtension(Target) & " " - If Target <> DefaultFolder Then CachedFolder(File.ResourceType) = PathUtils.RemoveLastPart(Target) + If Not PathUtils.IsSame(PathUtils.RemoveLastPart(Target), DefaultFolder) Then CachedFolder(File.ResourceType) = PathUtils.RemoveLastPart(Target) Dim Loaders As New List(Of LoaderBase) Loaders.Add(New LoaderDownload("下载文件", { File.ToNetFile(Target, If(TargetResourceType = ResourceTypes.Any, ResourceVersion.DownloadReason.Dependency, ResourceVersion.DownloadReason.Standalone)) }) With {.ProgressWeight = 6, .Block = True}) '启动 - Dim Loader As New LoaderCombo(Of Integer)(LoaderName, Loaders) With {.OnStateChanged = AddressOf LoaderStateChangedHintOnly} + Dim Loader As New LoaderCombo(Of Integer)(LoaderName, Loaders) With {.OnStateChanged = + Sub(MyLoader) + LoaderStateChangedHintOnly(MyLoader) + If MyLoader.State = LoadState.Finished AndAlso File.ResourceType = ResourceTypes.ModPack Then OpenExplorer(Target) + End Sub} Loader.Start(1) LoaderTaskbarAdd(Loader) FrmMain.BtnExtraDownload.ShowRefresh() @@ -510,29 +555,4 @@ GroupDone: PageLoaderRestart(IsForceRestart:=True) End Sub - ''' - ''' 预载包含大量 ProjectVersion 的卡片,添加必要的元素和前置列表。 - ''' - Public Shared Sub ResourceFilesCardPreload(Stack As StackPanel, Files As List(Of ResourceVersion)) - '获取卡片对应的前置 ID - '如果为整合包就不会有 Dependencies 信息,所以不用管 - Dim Deps As List(Of String) = Files.SelectMany(Function(f) f.Dependencies).Distinct.ToList() - Deps.Sort() - If Not Deps.Any() Then Return - Deps = Deps.Where( - Function(dep) - If Not ResourceProject.Cache.ContainsKey(dep) Then Logger.Warn($"未找到 ID {dep} 的前置信息") - Return ResourceProject.Cache.ContainsKey(dep) - End Function).ToList - '添加开头间隔 - Stack.Children.Add(New TextBlock With {.Text = "前置资源", .FontSize = 14, .HorizontalAlignment = HorizontalAlignment.Left, .Margin = New Thickness(6, 2, 0, 5)}) - '添加前置列表 - For Each Dep In Deps - Dim Item = ResourceProject.Cache(Dep).ToResourceItem(False, False) - Stack.Children.Add(Item) - Next - '添加结尾间隔 - Stack.Children.Add(New TextBlock With {.Text = "版本列表", .FontSize = 14, .HorizontalAlignment = HorizontalAlignment.Left, .Margin = New Thickness(6, 12, 0, 5)}) - End Sub - End Class diff --git a/Plain Craft Launcher 2/Pages/PageDownload/Resource/PageResource.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/Resource/PageResource.xaml.vb index fab3527..c8006c0 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/Resource/PageResource.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/Resource/PageResource.xaml.vb @@ -90,11 +90,11 @@ Public Class PageResource Next Return ComboSearchLoader.Items(0) End Function - If TargetInstance.Version.HasForge Then + If TargetInstance.Version.ModLoaders.HasFlagF(ModLoaders.Forge) Then ComboSearchLoader.SelectedItem = GetTargetItemByName("Forge") - ElseIf TargetInstance.Version.HasFabric Then + ElseIf TargetInstance.Version.ModLoaders.HasFlagF(ModLoaders.Fabric) Then ComboSearchLoader.SelectedItem = GetTargetItemByName("Fabric") - ElseIf TargetInstance.Version.HasNeoForge Then + ElseIf TargetInstance.Version.ModLoaders.HasFlagF(ModLoaders.NeoForge) Then ComboSearchLoader.SelectedItem = GetTargetItemByName("NeoForge") End If TargetInstance = Nothing diff --git a/Plain Craft Launcher 2/Pages/PageInstance/MyLocalModItem.xaml.vb b/Plain Craft Launcher 2/Pages/PageInstance/MyLocalModItem.xaml.vb index 412fb87..cc5f3ef 100644 --- a/Plain Craft Launcher 2/Pages/PageInstance/MyLocalModItem.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageInstance/MyLocalModItem.xaml.vb @@ -335,7 +335,7 @@ Public Class MyLocalModItem BtnUpdate.Visibility = Visibility.Collapsed End If '标题与描述 - Dim DescFileName As String = Entry.File.Name.BeforeLast(".") + Dim DescFileName As String = PathUtils.GetFileNameWithoutExtension(Entry.EnabledName) Dim NewDescription As String If Settings.Get(Of Integer)("ToolModLocalNameStyle") = 1 Then '标题显示文件名,详情显示译名 diff --git a/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceExport.xaml b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceExport.xaml index 42e779e..0c8c0e8 100644 --- a/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceExport.xaml +++ b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceExport.xaml @@ -65,10 +65,10 @@ + Title="OptiFine 设置" + Rules="optionsof.txt|optionsshaders.txt" + DefaultChecked="True" + RequireOptiFine="True" /> diff --git a/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceExport.xaml.vb b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceExport.xaml.vb index 9e05073..ee4db80 100644 --- a/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceExport.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceExport.xaml.vb @@ -22,14 +22,14 @@ Public Class PageInstanceExport End Sub Private Sub PageInstanceExport_Loaded() Handles Me.Loaded AniControlEnabled += 1 - If CurrentInstance <> PageInstanceLeft.Instance.PathVersion Then RefreshAll() '切换到了另一个版本,重置页面 + If Not PathUtils.IsSame(CurrentInstance, PageInstanceLeft.Instance.PathVersion) Then RefreshAll() '切换到了另一个版本,重置页面 CustomEventService.SetEventData(BtnAdvancedHelp, If(BuildType = BuildTypes.Release, "指南/整合包制作 - Public.json", "指南/整合包制作 - Snapshot.json")) AniControlEnabled -= 1 End Sub Public Sub RefreshAll() Implements IRefreshable.Refresh Logger.Info("刷新导出页面") - HintOptiFine.Visibility = If(PageInstanceLeft.Instance.Version.HasOptiFine, Visibility.Visible, Visibility.Collapsed) + HintOptiFine.Visibility = If(PageInstanceLeft.Instance.Version.HasOptiFine AndAlso Not PageInstanceLeft.Instance.Version.OptiFineAsMod, Visibility.Visible, Visibility.Collapsed) CurrentInstance = PageInstanceLeft.Instance.PathVersion TextExportName.Text = "" TextExportName.HintText = PageInstanceLeft.Instance.Name @@ -114,7 +114,7 @@ Public Class PageInstanceExport '确认选项是否应该被显示 Dim IsVisible = Function(TargetOption As ExportOption) As Boolean - '检查需要 OptiFine 或 Mod 加载器 + '检查需要 OptiFine(例如光影)或 Mod 加载器(例如 mods/) If TargetOption.RequireOptiFine AndAlso Not PageInstanceLeft.Instance.Version.HasOptiFine Then Return False If TargetOption.RequireModLoader AndAlso Not PageInstanceLeft.Instance.Modable Then Return False If TargetOption.RequireModLoaderOrOptiFine AndAlso Not PageInstanceLeft.Instance.Version.HasOptiFine AndAlso Not PageInstanceLeft.Instance.Modable Then Return False @@ -257,9 +257,9 @@ Public Class PageInstanceExport For Each ShaderPackCheckBox In PanOptionsShaderPacks.Children.OfType(Of MyCheckBox)() If Not ShaderPackCheckBox.Checked Then Continue For Dim ShaderPackOption = GetExportOption(ShaderPackCheckBox) - Dim SettingFileName = ShaderPackOption.Rules.TrimEnd("/") & ".txt" - If Not FileUtils.Exists(Path.Combine(PageInstanceLeft.Instance.PathIndie, SettingFileName)) Then Continue For - Rules.Add(StringUtils.LikePatternEscape($"shaderpacks/{SettingFileName}")) + Dim SettingsFile = $"shaderpacks/{ShaderPackOption.Title}.txt" + If Not FileUtils.Exists(Path.Combine(PageInstanceLeft.Instance.PathIndie, SettingsFile)) Then Continue For + Rules.Add(StringUtils.LikePatternEscape(SettingsFile)) Next End If @@ -457,11 +457,11 @@ Public Class PageInstanceExport '缓存所需参数 Dim CacheFolder = RequestTaskTempFolder() Dim OverridesFolder = CacheFolder & "modpack\overrides\" - Dim McVersion = PageInstanceLeft.Instance + Dim TargetInstance = PageInstanceLeft.Instance Dim IncludeJava As Boolean = (RulesOverrides Is Nothing AndAlso CheckOptionsJava.Visibility = Visibility.Visible AndAlso CheckOptionsJava.Checked) OrElse - (RulesOverrides IsNot Nothing AndAlso CheckOptionsJava.Checked AndAlso Settings.Get(Of Integer)("VersionArgumentJavaV2", McVersion) = 2) - Dim PathIndie As String = McVersion.PathIndie + (RulesOverrides IsNot Nothing AndAlso CheckOptionsJava.Checked AndAlso Settings.Get(Of Integer)("VersionArgumentJavaV2", TargetInstance) = 2) + Dim PathIndie As String = TargetInstance.PathIndie Dim CheckHostedAssets As Boolean = Not CheckAdvancedInclude.Checked Dim ModrinthUploadMode As Boolean = CheckAdvancedModrinth.Checked Dim IncludePCL As Boolean = CheckOptionsPcl.Checked @@ -498,7 +498,7 @@ Public Class PageInstanceExport '文件夹:进一步搜索 For Each SubFolder In Folder.EnumerateDirectories("*", SearchOption.TopDirectoryOnly) '跳过部分又没用文件又多的文件夹,加快搜索 - If {"assets", "versions", "libraries"}.Contains(SubFolder.Name) AndAlso PathIndie = PathUtils.RemoveExtendedPrefix(Folder.FullName) Then Continue For + If {"assets", "versions", "libraries"}.Contains(SubFolder.Name) AndAlso PathUtils.IsSame(PathIndie, Folder.FullName) Then Continue For If {"structureCacheV1", ".fabric", ".git", "avatar-cache", "cosmetic-cache"}.Contains(SubFolder.Name) Then Continue For SearchFolder(SubFolder) Next @@ -536,10 +536,10 @@ Public Class PageInstanceExport Loader.Progress = 0.85 '复制 Java If IncludeJava Then - Dim JavaToExport = SelectOrDownloadJava(McVersion, False, Loader.CreateCancellationToken, Loader.CreateSyncProgressProvider(0.85, 0.93)) + Dim JavaToExport = SelectOrDownloadJava(TargetInstance, False, Loader.CreateCancellationToken, Loader.CreateSyncProgressProvider(0.85, 0.93)) If JavaToExport Is Nothing Then Throw New FileNotFoundException("版本文件夹中没有找到可导出的 Java。") Dim JavaFolder = PathUtils.RemoveLastPart(JavaToExport.Folder) - Dim DestFolder As String = Path.Combine(OverridesFolder, JavaFolder.AfterFirst(McVersion.PathVersion, True)) + Dim DestFolder As String = Path.Combine(OverridesFolder, JavaFolder.AfterFirst(TargetInstance.PathVersion, True)) DirectoryUtils.Copy(JavaFolder, DestFolder) Logger.Info($"已复制版本文件夹中的 Java:{JavaFolder} → {DestFolder}") End If @@ -563,7 +563,7 @@ Public Class PageInstanceExport Next Loader.Progress = 0.97 '复制 PCL 版本设置 - If DirectoryUtils.Exists(McVersion.PathVersion & "PCL\") Then DirectoryUtils.Copy(McVersion.PathVersion & "PCL\", OverridesFolder & "PCL\") + If DirectoryUtils.Exists(TargetInstance.PathVersion & "PCL\") Then DirectoryUtils.Copy(TargetInstance.PathVersion & "PCL\", OverridesFolder & "PCL\") WriteIni(OverridesFolder & "PCL\Setup.ini", "IsStar", False) '复制 PCL 本体(正式版) If BuildType = BuildTypes.Release AndAlso IncludePCL Then FileUtils.Copy(PathExe, CacheFolder & "Plain Craft Launcher.exe") @@ -686,16 +686,16 @@ Public Class PageInstanceExport Next Loader.Progress = 0.2 '导出最终 JSON 文件 - Dim Dependencies As New JObject From {{"minecraft", McVersion.Version.VanillaName}} - If McVersion.Version.HasForge Then Dependencies.Add("forge", McVersion.Version.Forge) - If McVersion.Version.HasFabric Then Dependencies.Add("fabric-loader", McVersion.Version.Fabric) - If McVersion.Version.HasNeoForge Then Dependencies.Add("neoforge", McVersion.Version.NeoForge) + Dim Dependencies As New JObject From {{"minecraft", TargetInstance.Version.VanillaName}} + If TargetInstance.Version.ModLoaders.HasFlagF(ModLoaders.Forge) Then Dependencies.Add("forge", TargetInstance.Version.ModLoaderVersions(ModLoaders.Forge)) + If TargetInstance.Version.ModLoaders.HasFlagF(ModLoaders.Fabric) Then Dependencies.Add("fabric-loader", TargetInstance.Version.ModLoaderVersions(ModLoaders.Fabric)) + If TargetInstance.Version.ModLoaders.HasFlagF(ModLoaders.NeoForge) Then Dependencies.Add("neoforge", TargetInstance.Version.ModLoaderVersions(ModLoaders.NeoForge)) Dim ResultJson As New JObject From { {"game", "minecraft"}, {"formatVersion", 1}, {"versionId", PackVersion}, {"name", PackName}, - {"summary", McVersion.Info}, + {"summary", TargetInstance.Info}, {"files", Files}, {"dependencies", Dependencies} } diff --git a/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceLeft.xaml b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceLeft.xaml index b892217..41ad598 100644 --- a/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceLeft.xaml +++ b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceLeft.xaml @@ -13,21 +13,29 @@ - + - + + + + + + + - - + diff --git a/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceLeft.xaml.vb index b26065b..128367b 100644 --- a/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceLeft.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceLeft.xaml.vb @@ -6,6 +6,10 @@ Public Class PageInstanceLeft ''' Public Shared Instance As McInstance = Nothing + Private Sub PageInstanceLeft_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded + PageSetupUI.HiddenRefresh() + End Sub + Public Sub RefreshModDisabled() Handles Me.Loaded If Instance IsNot Nothing AndAlso Instance.Modable Then ItemMod.Visibility = Visibility.Visible @@ -26,7 +30,7 @@ Public Class PageInstanceLeft ''' ''' 勾选事件改变页面。 ''' - Private Sub PageCheck(sender As FrameworkElement, e As RouteEventArgs) Handles ItemOverall.Check, ItemMod.Check, ItemModDisabled.Check, ItemSetup.Check, ItemExport.Check + Private Sub PageCheck(sender As FrameworkElement, e As RouteEventArgs) Handles ItemOverall.Check, ItemMod.Check, ItemModDisabled.Check, ItemSetup.Check, ItemModify.Check, ItemExport.Check '尚未初始化控件属性时,sender.Tag 为 Nothing,会导致切换到页面 0 '若使用 IsLoaded,则会导致模拟点击不被执行(模拟点击切换页面时,控件的 IsLoaded 为 False) If sender.Tag IsNot Nothing Then PageChange(Val(sender.Tag)) @@ -45,11 +49,14 @@ Public Class PageInstanceLeft If FrmInstanceModDisabled Is Nothing Then FrmInstanceModDisabled = New PageInstanceModDisabled Return FrmInstanceModDisabled Case FormMain.PageSubType.InstanceSetup - If IsNothing(FrmInstanceSetup) Then FrmInstanceSetup = New PageInstanceSetup + If FrmInstanceSetup Is Nothing Then FrmInstanceSetup = New PageInstanceSetup Return FrmInstanceSetup Case FormMain.PageSubType.InstanceExport If FrmInstanceExport Is Nothing Then FrmInstanceExport = New PageInstanceExport Return FrmInstanceExport + Case FormMain.PageSubType.InstanceModify + If FrmDownloadModify Is Nothing Then FrmDownloadModify = New PageDownloadModify + Return FrmDownloadModify Case Else Throw New Exception("未知的版本设置子页面种类:" & ID) End Select @@ -60,6 +67,10 @@ Public Class PageInstanceLeft ''' Public Sub PageChange(ID As FormMain.PageSubType) If PageID = ID Then Return + If Instance IsNot Nothing AndAlso McInstanceLock.IsLocked(Instance) Then + Hint("该版本正在被修改,请稍后再切换页面!", HintType.Red) + Return + End If AniControlEnabled += 1 Try PageChangeRun(PageGet(ID)) @@ -98,13 +109,20 @@ Public Class PageInstanceLeft Refresh(FrmMain.PageCurrentSub) End Sub Public Sub Refresh(SubType As FormMain.PageSubType) + If Instance IsNot Nothing AndAlso McInstanceLock.IsLocked(Instance) Then + Hint("该版本正在被修改,请稍后再刷新!", HintType.Red) + Return + End If Select Case SubType Case FormMain.PageSubType.InstanceMod PageInstanceMod.Refresh() ItemMod.Checked = True Case FormMain.PageSubType.InstanceExport - If FrmInstanceExport IsNot Nothing Then FrmInstanceExport.RefreshAll() + FrmInstanceExport?.RefreshAll() ItemExport.Checked = True + Case FormMain.PageSubType.InstanceModify + FrmDownloadModify?.PageLoaderRestart() + ItemModify.Checked = True End Select End Sub diff --git a/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceMod.xaml.vb b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceMod.xaml.vb index d320c58..b5e2422 100644 --- a/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceMod.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceMod.xaml.vb @@ -284,7 +284,7 @@ Public Class PageInstanceMod ''' 返回输入的文件是否为一个 Mod 文件,仅用于判断拖拽行为。 ''' Public Shared Function InstallMods(FilePathList As IEnumerable(Of String)) As Boolean - Dim Extension As String = FilePathList.First.AfterLast(".").Lower + Dim Extension As String = PathUtils.GetExtension(FilePathList.First) '检查文件扩展名 If Not {"jar", "litemod", "disabled", "old"}.Any(Function(t) t = Extension) Then Return False Logger.Info("文件为 jar/litemod 格式,尝试作为 Mod 安装") @@ -307,15 +307,15 @@ Public Class PageInstanceMod Install: Try For Each ModFile In FilePathList - Dim NewFileName = PathUtils.GetLastPart(ModFile).Replace(".disabled", "").Replace(".old", "") + Dim NewFileName = (New LocalResourceFile(ModFile)).EnabledName If Not NewFileName.Contains(".") Then NewFileName += ".jar" '#4227 NewFileName = Instance.PathIndie & "mods\" & NewFileName - If ModFile = NewFileName Then Continue For '防止安装相同文件导致文件被删除 + If PathUtils.IsSame(ModFile, NewFileName) Then Continue For '防止安装相同文件导致文件被删除 FileUtils.Delete(NewFileName, True) FileUtils.Copy(ModFile, NewFileName) Next If FilePathList.IsSingle Then - Hint($"已安装 {PathUtils.GetLastPart(FilePathList.First).Replace(".disabled", "").Replace(".old", "")}!", HintType.Green) + Hint($"已安装 {(New LocalResourceFile(FilePathList.First)).EnabledName}!", HintType.Green) Else Hint($"已安装 {FilePathList.Count} 个 Mod!", HintType.Green) End If @@ -553,8 +553,8 @@ Install: Dim File As ResourceVersion = Entry.UpdateFile If Not File.DownloadAvailable Then Continue For '确认更新后的文件名 - Dim CurrentReplaceName = Entry.ProjectVersion.FileName.Replace(".jar", "").Replace(".old", "").Replace(".disabled", "") - Dim NewestReplaceName = Entry.UpdateFile.FileName.Replace(".jar", "").Replace(".old", "").Replace(".disabled", "") + Dim CurrentReplaceName = PathUtils.GetFileNameWithoutExtension(Entry.ProjectVersion.FileName) + Dim NewestReplaceName = PathUtils.GetFileNameWithoutExtension(Entry.UpdateFile.FileName) Dim CurrentSegs = CurrentReplaceName.Split("-"c).ToList() Dim NewestSegs = NewestReplaceName.Split("-"c).ToList() Dim Shortened As Boolean = False diff --git a/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceOverall.xaml.vb b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceOverall.xaml.vb index 3c160cc..1e3703b 100644 --- a/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceOverall.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceOverall.xaml.vb @@ -104,51 +104,21 @@ Public Class PageInstanceOverall '重命名版本 Private Sub BtnDisplayRename_Click(sender As Object, e As EventArgs) Handles BtnDisplayRename.Click Try + If McInstanceLock.IsLocked(PageInstanceLeft.Instance) Then + Hint("该版本正在被修改,请稍后再重命名!", HintType.Red) + Return + End If '确认输入的新名称 Dim OldName As String = PageInstanceLeft.Instance.Name - Dim OldPath As String = PageInstanceLeft.Instance.PathVersion '修改此部分的同时修改快速安装的版本名检测* Dim NewName As String = MyMsgBoxInput("重命名版本", "", OldName, New ObjectModel.Collection(Of Validate) From { New ValidateFolderName(McFolderSelected & "versions", IgnoreList:=New List(Of String) From {OldName}) }) If String.IsNullOrWhiteSpace(NewName) Then Return - Dim NewPath As String = McFolderSelected & "versions\" & NewName & "\" - '重新读取版本 JSON 信息,避免 JsonObject 中已被合并的项被重新存储 - Dim JsonObject As JObject - Dim OldJsonPath As String = PageInstanceLeft.Instance.GetJsonPath() - Try - JsonObject = FileUtils.ReadAsJson(OldJsonPath) - Catch ex As Exception - Logger.Warn(ex, "在重命名读取 JSON 时失败") - JsonObject = PageInstanceLeft.Instance.JsonObject - End Try - '重命名主文件夹 - DirectoryUtils.Move(OldPath, NewPath) - '清理 ini 缓存 - PageInstanceLeft.Instance.ResetSettingsCache() - '重命名 jar 文件与 natives 文件夹 - '不能进行遍历重命名,否则在版本名很短的时候容易误伤其他文件(#6443) - If DirectoryUtils.Exists($"{NewPath}{OldName}-natives") Then DirectoryUtils.Move($"{NewPath}{OldName}-natives", $"{NewPath}{NewName}-natives") - If FileUtils.Exists($"{NewPath}{OldName}.jar") Then FileUtils.Move($"{NewPath}{OldName}.jar", $"{NewPath}{NewName}.jar") - '替换版本设置文件中的路径 - If FileUtils.Exists(NewPath & "PCL\Setup.ini") Then - FileUtils.Write(NewPath & "PCL\Setup.ini", FileUtils.ReadAsString(NewPath & "PCL\Setup.ini").Replace(OldPath, NewPath)) - End If - '更改已选中的版本 - If ReadIni(McFolderSelected & "PCL.ini", "Version") = OldName Then - WriteIni(McFolderSelected & "PCL.ini", "Version", NewName) - End If - '更新版本 Json - Try - JsonObject("id") = NewName - FileUtils.Delete(NewPath & PathUtils.GetLastPart(OldJsonPath)) - FileUtils.Write(NewPath & NewName & ".json", JsonObject.ToString) - Catch ex As Exception - Logger.Warn(ex, "重命名版本 json 失败") - End Try + Dim NewInstance = PageInstanceLeft.Instance.Rename(NewName) '刷新与提示 Hint("重命名成功!", HintType.Green) - PageInstanceLeft.Instance = New McInstance(NewName).Load() + PageInstanceLeft.Instance = NewInstance If Not IsNothing(McInstanceSelected) AndAlso McInstanceSelected.Equals(PageInstanceLeft.Instance) Then WriteIni(McFolderSelected & "PCL.ini", "Version", NewName) Reload() LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") @@ -267,6 +237,10 @@ Public Class PageInstanceOverall '补全文件 Private Sub BtnManageCheck_Click(sender As Object, e As EventArgs) Handles BtnManageCheck.Click Try + If McInstanceLock.IsLocked(PageInstanceLeft.Instance) Then + Hint("该版本正在被修改,请稍后再补全文件!", HintType.Red) + Return + End If '忽略文件检查提示 If ShouldIgnoreFileCheck(PageInstanceLeft.Instance) Then Hint("请先关闭 [版本设置 → 设置 → 高级启动选项 → 关闭文件校验],然后再尝试补全文件!", HintType.Blue) @@ -304,8 +278,12 @@ Public Class PageInstanceOverall '修改此代码时,同时修改 PageSelectRight 中的代码 Private Sub BtnManageDelete_Click(sender As Object, e As EventArgs) Handles BtnManageDelete.Click Try + If McInstanceLock.IsLocked(PageInstanceLeft.Instance) Then + Hint("该版本正在被修改,请稍后再删除!", HintType.Red) + Return + End If Dim IsShiftPressed As Boolean = My.Computer.Keyboard.ShiftKeyDown - Dim IsHintIndie As Boolean = PageInstanceLeft.Instance.State <> McInstanceState.Error AndAlso PageInstanceLeft.Instance.PathIndie <> McFolderSelected + Dim IsHintIndie As Boolean = PageInstanceLeft.Instance.State <> McInstance.McInstanceState.Error AndAlso PageInstanceLeft.Instance.PathIndie <> McFolderSelected Dim SavesFolder = DirectoryUtils.GetInfo(PageInstanceLeft.Instance.PathIndie & "saves\") Dim SaveEntries = If(IsHintIndie AndAlso SavesFolder.Exists, SavesFolder.EnumerateDirectories.OrderByDescending(Function(Save) Save.LastWriteTime).Select(Function(Save) $"{Save.Name}(上次修改:{StringUtils.FormatTimeSpan(Save.LastWriteTime - Date.Now, False)})").ToList, New List(Of String)) Select Case MyMsgBox($"你确定要{If(IsShiftPressed, "永久", "")}删除版本 {PageInstanceLeft.Instance.Name} 吗?" & diff --git a/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSetup.xaml.vb b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSetup.xaml.vb index cd31525..3146967 100644 --- a/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSetup.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSetup.xaml.vb @@ -499,7 +499,7 @@ PreFin: ComboArgumentJavaSelect.Items.Add(JavaItem) Dim Buttons As New List(Of MyIconButton) '从列表中移除按钮 - Dim IsOfficial As Boolean = JavaEntry.Folder.StartsWithF($"{Paths.AppData}.minecraft\runtime\") + Dim IsOfficial As Boolean = PathUtils.IsParentOf($"{Paths.AppData}.minecraft\runtime\", JavaEntry.Folder) Dim DeleteButton As New MyIconButton With {.Logo = Logo.IconButtonStop, .Height = 24, .Width = 24} DeleteButton.IsEnabled = Not IsOfficial DeleteButton.ToolTip = If(DeleteButton.IsEnabled, "从列表中移除", "无法移除官方 Java") diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.vb index f8bccb2..e21af62 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.vb @@ -65,7 +65,7 @@ Public Class PageLaunchLeft '确认 Minecraft 版本存在 Dim Selection As String = ReadIni(McFolderSelected & "PCL.ini", "Version") Dim Instance As McInstance = If(Selection = "", Nothing, New McInstance(Selection)) - If Instance Is Nothing OrElse Not Instance.PathVersion.StartsWithF(McFolderSelected) OrElse Not Instance.Check() Then + If Instance Is Nothing OrElse Not PathUtils.IsParentOf(McFolderSelected, Instance.PathVersion) OrElse Not Instance.Check() Then '无效的版本 Logger.Info($"当前选择的 Minecraft 版本无效:{If(Instance Is Nothing, "null", Instance.PathVersion)}", If(IsNothing(Instance), LogBehavior.None, LogBehavior.ToastIfDebug)) If Not McInstanceListLoader.State = LoadState.Finished Then LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\", WaitForExit:=True) @@ -691,12 +691,28 @@ ExitRefresh: End If End Sub '取消按钮 - Private Sub BtnCancel_Click() Handles BtnCancel.Click - McLaunchCancel() + Public Sub BtnCancel_Click() Handles BtnCancel.Click + If McLaunchLoaderReal IsNot Nothing Then + McLaunchLoaderReal.Cancel() + McLaunchLog("已取消启动") + Try + If McLaunchWatcher IsNot Nothing Then + McLaunchWatcher.Kill() + ElseIf McLaunchProcess IsNot Nothing Then + If Not McLaunchProcess.HasExited Then McLaunchProcess.Kill() + End If + Catch ex As Exception + Logger.Error(ex, "取消启动结束进程失败", LogBehavior.Toast) + End Try + End If End Sub '版本设置按钮 Private Sub BtnMore_Click() Handles BtnMore.Click If McLaunchLoader.State = LoadState.Loading Then Return + If McInstanceLock.IsLocked(McInstanceSelected) Then + Hint("该版本正在被修改,暂时无法进入版本设置!", HintType.Red) + Return + End If McInstanceSelected.Load() PageInstanceLeft.Instance = McInstanceSelected FrmMain.PageChange(FormMain.PageType.InstanceSetup, 0) diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml index 56c4a1a..6f3cc54 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml @@ -3,6 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:behaviors="clr-namespace:PCL.Controls.Behaviors" xmlns:local="clr-namespace:PCL" PanScroll="PanBack"> @@ -11,8 +12,10 @@ - + - \ No newline at end of file + diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb index b796642..477ac66 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb @@ -101,19 +101,34 @@ Public Class PageLaunchRight Url = "https://pcl.wyc-w.top/index.xaml" Case 15 Logger.Info("主页预设:Minecraft 芝士站") - Url = "https://www.xxag.top/mkss" + Url = "https://mkss-services.awaeric.xyz" Case 16 Logger.Info("主页预设:整合包推荐引擎") Url = "https://qawsedrftgyhujiko.fun/pcl2/Custom.xaml" Case 17 - Logger.Info("Bangumi 番剧主页") - Url = "https://bangumi.p.kaphia.qzz.io" + Logger.Info("主页预设:Bangumi 番剧主页") + Url = "https://bangumi.p.kaphia.top" Case 18 - Logger.Info("Bilibili 热门") - Url = "https://bilibili.p.kaphia.qzz.io" + Logger.Info("主页预设:Bilibili 热门") + Url = "https://bilibili.p.kaphia.top" Case 19 - Logger.Info("Music 云音乐热门") - Url = "https://cloudmusic.p.kaphia.qzz.io" + Logger.Info("主页预设:Music 云音乐热门") + Url = "https://cloudmusic.p.kaphia.top" + Case 20 + Logger.Info("主页预设:模组推荐") + Url = "https://g-fish.dpdns.org/download/ModPage.xaml" + Case 21 + Logger.Info("主页预设:每日游戏推荐") + Url = "https://pcl.xn--9iq209asnop94b.top/download/GamePage.xaml" + Case 22 + Logger.Info("主页预设:Wallpaper 每日壁纸") + Url = "https://wallpaper.p.kaphia.top" + Case 23 + Logger.Info("主页预设:Weather 天气预报") + Url = "https://weather-p.kaphia.top/{variable:PCLDailyWeatherPage:choose}.xaml" + Case 24 + Logger.Info("主页预设:新一代狗屁王大杂烩主页") + Url = "https://end.g-fish.dpdns.org/Custom.xaml" End Select End Select '联网下载 diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb index 64b467b..36fe002 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb @@ -45,7 +45,7 @@ Public Class PageLoginLegacy If LoginData.UserName.Trim = "" Then Return "玩家名不能为空!" If LoginData.UserName.Contains("""") Then Return "玩家名不能包含英文引号!" If McInstanceSelected IsNot Nothing AndAlso - McInstanceSelected.Version.Vaild AndAlso McInstanceSelected.Version.Vanilla >= New Version(20, 0, 3) AndAlso + McInstanceSelected.Version.Valid AndAlso McInstanceSelected.Version.Vanilla >= New Version(20, 0, 3) AndAlso LoginData.UserName.Trim.Length > 16 Then Return "自 1.20.3 起,玩家名至多只能包含 16 个字符!" End If diff --git a/Plain Craft Launcher 2/Pages/PageLink/PageLinkMain.xaml.vb b/Plain Craft Launcher 2/Pages/PageLink/PageLinkMain.xaml.vb index 43f66de..447dbcc 100644 --- a/Plain Craft Launcher 2/Pages/PageLink/PageLinkMain.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLink/PageLinkMain.xaml.vb @@ -358,8 +358,9 @@ Public Class PageLinkMain '复制邀请码 Private Sub Copy() Handles BtnFinishCopy.Click Dim CodeText As String = $"在 PCL 启动器中输入邀请码【{GetInviteCode()}】,即可加入联机房间!" - ClipboardSet(CodeText, SuccessHint:="已复制邀请码!") + ClipboardSet(CodeText, False) Settings.Set("LinkLastAutoJoinInviteCode", CodeText) + Hint("已复制邀请码!", HintType.Green) End Sub Private Function GetInviteCode() As String Return $"{NetworkName}-{NetworkSecret}-{INVITE_CODE_VERSION.ToString.PadLeft(2, "0"c)}{ _ @@ -368,7 +369,8 @@ Public Class PageLinkMain '复制 IP Private Sub BtnFinishIp_MouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs) Handles BtnFinishIp.MouseLeftButtonUp - ClipboardSet(ClientAddress, SuccessHint:="已复制服务器地址!") + ClipboardSet(ClientAddress, False) + Hint("已复制服务器地址!", HintType.Green) End Sub #End Region diff --git a/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml b/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml index 29e5481..4cdff7e 100644 --- a/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml +++ b/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml @@ -26,7 +26,7 @@ - + @@ -131,98 +131,64 @@ - - - - + + + + + + - - + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + - - - - + + + + + + + + + + + - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -232,76 +198,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - + + + - - - - - + + + + + + + + + + + - + + + - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + @@ -309,211 +365,400 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + - - - + + + + + + + - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + - - - - - + + + + + + + + + + + + - - + + + + - + - - - - - - + + + + + + + + + + + + + + @@ -600,14 +845,14 @@ local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://opensource.org/licenses/MIT" /> - + - + - - + - - - + + - - - - + + - + + + + + diff --git a/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelp.xaml.vb b/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelp.xaml.vb index 6e321be..ae3e101 100644 --- a/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelp.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelp.xaml.vb @@ -48,12 +48,13 @@ Public Class PageOtherHelp If Item.Types.Contains(Type) Then TypeItems.Add(Item) Next '增加卡片 - Dim NewCard As New MyCard With {.Title = Type, .Margin = New Thickness(0, 0, 0, 15), .SwapType = 11} - Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = TypeItems} + Dim NewCard As New MyCard With {.Title = Type, .Margin = New Thickness(0, 0, 0, 15)} + Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0)} NewCard.Children.Add(NewStack) NewCard.SwapControl = NewStack + NewCard.SetContentBuilder(TypeItems, Function(Entry) Entry.ToListItem) If Type = "指南" Then - MyCard.StackInstall(NewStack, 11, "指南") + NewCard.BuildContent() Else NewCard.IsSwapped = True End If diff --git a/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb index e3f2df7..edb1a0e 100644 --- a/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb @@ -64,7 +64,7 @@ Public Class PageSelectLeft ) End Select - If (Folder.Type = McFolder.Types.Vanilla OrElse Folder.Type = McFolder.Types.RenamedVanilla) AndAlso Folder.Location = Paths.Base & ".minecraft\" AndAlso McFolderList.IsSingle Then CType(ContMenu.FindName("Delete"), MyMenuItem).Header = "清空" + If (Folder.Type = McFolder.Types.Vanilla OrElse Folder.Type = McFolder.Types.RenamedVanilla) AndAlso PathUtils.IsSame(Folder.Location, Paths.Base & ".minecraft\") AndAlso McFolderList.IsSingle Then CType(ContMenu.FindName("Delete"), MyMenuItem).Header = "清空" '注册事件 If Not Folder.Type = McFolder.Types.Vanilla Then CType(ContMenu.FindName("Remove"), MyMenuItem).AddHandler(MyMenuItem.ClickEvent, New RoutedEventHandler(AddressOf FrmSelectLeft.Remove_Click)) CType(ContMenu.FindName("Open"), MyMenuItem).AddHandler(MyMenuItem.ClickEvent, New RoutedEventHandler(AddressOf FrmSelectLeft.Open_Click)) @@ -116,7 +116,7 @@ Public Class PageSelectLeft '确认勾选状态 For i = 0 To McFolderList.Count - 1 - If McFolderList(i).Location = McFolderSelected Then + If PathUtils.IsSame(McFolderList(i).Location, McFolderSelected) Then CType(FrmSelectLeft.PanList.Children(i + 1), MyListItem).Checked = True '去掉第一个标题 Return End If @@ -139,9 +139,9 @@ Public Class PageSelectLeft '添加文件夹 Public Sub Add_Click() Dim NewFolder As String = "" - '检查是否有下载任务 - If HasDownloadingTask() Then - Hint("在下载任务进行时,无法添加游戏文件夹!", HintType.Red) + '下载或版本修改期间禁止更改游戏文件夹 + If McInstanceLock.IsAnyLocked OrElse HasDownloadingTask() Then + Hint("请在其他任务完成后,再添加游戏文件夹!", HintType.Red) Return End If Try @@ -150,8 +150,11 @@ Public Class PageSelectLeft If NewFolder Is Nothing Then Return If NewFolder.Contains("!") OrElse NewFolder.Contains(";") Then Hint("Minecraft 文件夹路径中不能含有感叹号或分号!", HintType.Red) : Return '要求输入显示名称 - Dim SplitedNames As String() = NewFolder.TrimEnd("\").Split("\") - Dim DefaultName As String = If(SplitedNames.Last = ".minecraft", If(SplitedNames.Count >= 3, SplitedNames(SplitedNames.Count - 2), ""), SplitedNames.Last) + Dim DefaultName = PathUtils.GetLastPart(NewFolder) + If DefaultName = ".minecraft" Then + Dim ParentFolder = PathUtils.RemoveLastPart(NewFolder) + DefaultName = If(ParentFolder.EndsWithF(":"), "", PathUtils.GetLastPart(ParentFolder)) + End If If DefaultName.Length > 40 Then DefaultName = DefaultName.Substring(0, 39) Dim NewName As String = MyMsgBoxInput("输入显示名称", "输入该文件夹在左边栏列表中显示的名称。", DefaultName, New ObjectModel.Collection(Of Validate) From {New ValidateNullOrWhiteSpace, New ValidateLength(1, 30), New ValidateExcept({">", "|"})}) @@ -169,7 +172,7 @@ Public Class PageSelectLeft RunInThread( Sub() Try - If Not FolderPath.EndsWithF("\") Then FolderPath &= "\" + FolderPath = PathUtils.AddSlashSuffix(FolderPath) '检查文件夹权限 If Not CheckPermission(FolderPath) Then If ShowHint Then @@ -195,7 +198,7 @@ Public Class PageSelectLeft For i = 0 To Folders.Count - 1 Dim Folder As String = Folders(i) If Folder = "" Then Continue For - If Folder.Split(">")(1) = FolderPath Then + If PathUtils.IsSame(Folder.Split(">")(1), FolderPath) Then IsAdded = True If Folder.Split(">")(0) = DisplayName Then If ShowHint Then Hint("此文件夹已在列表中!", HintType.Blue) @@ -246,9 +249,9 @@ Public Class PageSelectLeft '创建文件夹 Public Sub Create_Click() - '检查是否有下载任务 - If HasDownloadingTask() Then - Hint("在下载任务进行时,无法创建游戏文件夹!", HintType.Red) + '下载或版本修改期间禁止更改游戏文件夹 + If McInstanceLock.IsAnyLocked OrElse HasDownloadingTask() Then + Hint("请在其他任务完成后,再创建游戏文件夹!", HintType.Red) Return End If CreateMcFolderInCurrentPath() @@ -270,6 +273,10 @@ Public Class PageSelectLeft Dim Target As McFolder = CType(CType(CType(sender.Parent, ContextMenu).Parent, Primitives.Popup).PlacementTarget, MyListItem).Tag '若为 “移除”,则提醒是否删除 PCL 的配置文件 If Target.Type = McFolder.Types.Custom Then + If McInstanceLock.IsAnyLocked OrElse HasDownloadingTask() Then + Hint("请在其他任务完成后,再移除游戏文件夹!", HintType.Red) + Return + End If Select Case MyMsgBox("是否需要清理 PCL 在该文件夹中的配置文件?" & vbCrLf & "这包括各个版本的独立设置(如自定义图标、第三方登录配置)等,对游戏本身没有影响。", "配置文件清理", "删除", "保留", "取消") Case 1 '删除配置文件 @@ -292,6 +299,10 @@ Public Class PageSelectLeft End Try End Sub Public Sub Delete_Click(sender As Object, e As RoutedEventArgs) + If McInstanceLock.IsAnyLocked OrElse HasDownloadingTask() Then + Hint("请在其他任务完成后,再删除游戏文件夹!", HintType.Red) + Return + End If Dim Target As McFolder = CType(CType(CType(sender.Parent, ContextMenu).Parent, Primitives.Popup).PlacementTarget, MyListItem).Tag Dim DeleteText As String = If((Target.Type = McFolder.Types.Vanilla OrElse Target.Type = McFolder.Types.RenamedVanilla) AndAlso Target.Location = Paths.Base & ".minecraft\" AndAlso McFolderList.IsSingle, "清空", "删除") If MyMsgBox("你确定要" & DeleteText & "这个文件夹吗?" & vbCrLf & @@ -328,7 +339,7 @@ Public Class PageSelectLeft Dim DeletedName As String = Nothing Dim Folders = Settings.Get(Of String)("LaunchFolders").Split("|"c, True).ToList For Each Folder In Folders.ToList - If Not Folder.EndsWithF(Target.Location) Then Continue For + If Not Folder.Contains(">") OrElse Not PathUtils.IsSame(Folder.AfterFirst(">"), Target.Location) Then Continue For DeletedName = Folder.BeforeFirst(">") Folders.Remove(Folder) Exit For @@ -349,7 +360,7 @@ Public Class PageSelectLeft End Sub Public Shared Sub RefreshCurrent(Folder As String) WriteIni(Folder & "PCL.ini", "InstanceCache", "") '删除缓存以强制要求下一次加载时更新列表 - If Folder = McFolderSelected Then LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") + If PathUtils.IsSame(Folder, McFolderSelected) Then LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") End Sub Public Sub Rename_Click(sender As Object, e As RoutedEventArgs) Dim Folder As McFolder = CType(CType(CType(sender.Parent, ContextMenu).Parent, Primitives.Popup).PlacementTarget, MyListItem).Tag @@ -365,7 +376,7 @@ Public Class PageSelectLeft For i = 0 To Folders.Count - 1 Dim FolderCurrent As String = Folders(i) If FolderCurrent = "" Then Continue For - If FolderCurrent.Split(">")(1) = Folder.Location Then + If PathUtils.IsSame(FolderCurrent.Split(">")(1), Folder.Location) Then IsAdded = True If FolderCurrent.Split(">")(0) = NewName Then '名称未修改 @@ -390,9 +401,9 @@ Public Class PageSelectLeft '点击选项 Public Sub Folder_Change(sender As MyListItem, e As RouteEventArgs) If Not e.RaiseByMouse OrElse Not sender.Checked Then Return - '检查是否有下载任务 - If HasDownloadingTask(True) Then - Hint("在下载任务进行时,无法切换游戏文件夹!", HintType.Red) + '下载或版本修改期间禁止更改游戏文件夹 + If McInstanceLock.IsAnyLocked OrElse HasDownloadingTask(True) Then + Hint("请在其他任务完成后,再切换游戏文件夹!", HintType.Red) e.Handled = True Return End If diff --git a/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb b/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb index 63ae8c5..3a17d9e 100644 --- a/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb @@ -30,6 +30,8 @@ Public Class PageSelectRight For Each Card As KeyValuePair(Of McInstanceCardType, List(Of McInstance)) In McInstanceList.ToArray '确认是否为隐藏版本显示状态 If Card.Key = McInstanceCardType.Hidden Xor ShowHidden Then Continue For + Dim Instances = Card.Value.Where(Function(Instance) Not McInstanceLock.IsLocked(Instance)).ToList + If Not Instances.Any Then Continue For #Region "确认卡片名称" Dim CardName As String = "" Select Case Card.Key @@ -40,11 +42,11 @@ Public Class PageSelectRight Dim IsNeoForgeExists As Boolean = False Dim IsFabricExists As Boolean = False Dim IsLiteExists As Boolean = False - For Each Instance As McInstance In Card.Value - If Instance.Version.HasFabric Then IsFabricExists = True - If Instance.Version.HasLiteLoader Then IsLiteExists = True - If Instance.Version.HasForge Then IsForgeExists = True - If Instance.Version.HasNeoForge Then IsNeoForgeExists = True + For Each Instance As McInstance In Instances + If Instance.Version.ModLoaders.HasFlagF(ModLoaders.Fabric) Then IsFabricExists = True + If Instance.Version.ModLoaders.HasFlagF(ModLoaders.LiteLoader) Then IsLiteExists = True + If Instance.Version.ModLoaders.HasFlagF(ModLoaders.Forge) Then IsForgeExists = True + If Instance.Version.ModLoaders.HasFlagF(ModLoaders.NeoForge) Then IsNeoForgeExists = True Next If If(IsLiteExists, 1, 0) + If(IsForgeExists, 1, 0) + If(IsFabricExists, 1, 0) + If(IsNeoForgeExists, 1, 0) > 1 Then CardName = "可安装 Mod" @@ -72,17 +74,18 @@ Public Class PageSelectRight End Select #End Region '建立控件 - Dim CardTitle As String = CardName & If(CardName = "收藏夹", "", " (" & Card.Value.Count & ")") - Dim NewCard As New MyCard With {.Title = CardTitle, .Margin = New Thickness(0, 0, 0, 15), .SwapType = 0} - Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = Card.Value} + Dim CardTitle As String = CardName & If(CardName = "收藏夹", "", " (" & Instances.Count & ")") + Dim NewCard As New MyCard With {.Title = CardTitle, .Margin = New Thickness(0, 0, 0, 15)} + Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0)} NewCard.Children.Add(NewStack) NewCard.SwapControl = NewStack + NewCard.SetContentBuilder(Instances, Function(Instance) Instance.ToListItem(AddressOf McInstanceListContent)) PanMain.Children.Add(NewCard) '确定卡片是否展开 If Card.Key = McInstanceCardType.Rubbish OrElse Card.Key = McInstanceCardType.Error OrElse Card.Key = McInstanceCardType.Fool Then NewCard.IsSwapped = True Else - MyCard.StackInstall(NewStack, 0, CardTitle) + NewCard.BuildContent() End If Next @@ -128,15 +131,16 @@ Public Class PageSelectRight BtnStar.LogoScale = 1.1 BtnStar.Logo = Logo.IconButtonLikeLine End If - AddHandler BtnStar.Click, Sub() - WriteIni(Instance.PathVersion & "PCL\Setup.ini", "IsStar", Not Instance.IsStar) - McInstanceListForceRefresh = True - LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") - End Sub + AddHandler BtnStar.Click, + Sub() + WriteIni(Instance.PathVersion & "PCL\Setup.ini", "IsStar", Not Instance.IsStar) + McInstanceListForceRefresh = True + LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") + End Sub Dim BtnDel As New MyIconButton With {.LogoScale = 1.1, .Logo = Logo.IconButtonDelete} BtnDel.ToolTip = "删除" AddHandler BtnDel.Click, Sub() DeleteInstance(sender, Instance) - If Instance.State <> McInstanceState.Error Then + If Instance.State <> McInstance.McInstanceState.Error Then Dim BtnCont As New MyIconButton With {.LogoScale = 1.1, .Logo = Logo.IconButtonSetup} BtnCont.ToolTip = "设置" AddHandler BtnCont.Click, @@ -166,6 +170,10 @@ Public Class PageSelectRight '点击选项 Public Shared Sub Item_Click(sender As MyListItem, e As EventArgs) Dim Instance As McInstance = sender.Tag + If McInstanceLock.IsLocked(Instance) Then + Hint("该版本正在被修改,请稍后再选择!", HintType.Red) + Return + End If If New McInstance(Instance.PathVersion).Check Then '正常版本 McInstanceSelected = Instance @@ -183,8 +191,12 @@ Public Class PageSelectRight '修改此代码时,同时修改 PageInstanceOverall 中的代码 Public Shared Sub DeleteInstance(Item As MyListItem, Instance As McInstance) Try + If McInstanceLock.IsLocked(Instance) Then + Hint("该版本正在被修改,请稍后再删除!", HintType.Red) + Return + End If Dim IsShiftPressed As Boolean = My.Computer.Keyboard.ShiftKeyDown - Dim IsHintIndie As Boolean = Instance.State <> McInstanceState.Error AndAlso Instance.PathIndie <> McFolderSelected + Dim IsHintIndie As Boolean = Instance.State <> McInstance.McInstanceState.Error AndAlso Instance.PathIndie <> McFolderSelected Dim SavesFolder = DirectoryUtils.GetInfo(Instance.PathIndie & "saves\") Dim SaveEntries = If(IsHintIndie AndAlso SavesFolder.Exists, SavesFolder.EnumerateDirectories.OrderByDescending(Function(Save) Save.LastWriteTime).Select(Function(Save) $"{Save.Name}(上次修改:{StringUtils.FormatTimeSpan(Save.LastWriteTime - Date.Now, False)})").ToList, New List(Of String)) Select Case MyMsgBox($"你确定要{If(IsShiftPressed, "永久", "")}删除版本 {Instance.Name} 吗?" & @@ -207,7 +219,7 @@ Public Class PageSelectRight Dim Card As MyCard = Parent.Parent Card.Title = Card.Title.Replace(Parent.Children.Count - 1, Parent.Children.Count - 2) '有一个占位符 Parent.Children.Remove(Item) - If McInstanceSelected IsNot Nothing AndAlso Instance.PathVersion = McInstanceSelected.PathVersion Then + If McInstanceSelected IsNot Nothing AndAlso PathUtils.IsSame(Instance.PathVersion, McInstanceSelected.PathVersion) Then '删除当前版本就更改选择 McInstanceSelected = MyVirtualizingElement.TryInit(Parent.Children(0)).Tag End If diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml index c1b69c6..1080ecc 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml @@ -27,8 +27,6 @@ - - - @@ -291,6 +286,7 @@ + diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.vb b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.vb index 542e423..b1139ae 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.vb @@ -396,7 +396,7 @@ PreFin: AddHandler DownButton.Click, Sub() MoveJavaInList(JavaEntry, 1) Buttons.Add(DownButton) '从列表中移除按钮 - Dim IsOfficial As Boolean = JavaEntry.Folder.StartsWithF($"{Paths.AppData}.minecraft\runtime\") + Dim IsOfficial As Boolean = PathUtils.IsParentOf($"{Paths.AppData}.minecraft\runtime\", JavaEntry.Folder) Dim DeleteButton As New MyIconButton With {.Logo = Logo.IconButtonStop, .Height = 24, .Width = 24} DeleteButton.IsEnabled = Not IsOfficial DeleteButton.ToolTip = If(DeleteButton.IsEnabled, "从列表中移除", "无法移除官方 Java") diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml index b3eb662..45d20db 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml @@ -235,18 +235,16 @@ - - - - - - - - - - - - + + + + + + @@ -288,6 +286,11 @@ + + + + + @@ -334,6 +337,7 @@ + diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb index 3683f65..6a522d9 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb @@ -306,19 +306,6 @@ Refresh: "你可以在生成教学文件后直接刷新主页,对照着进行修改,更有助于理解。" & vbCrLf & "直接将主页文件拖进 PCL 窗口也可以快捷加载。", "主页自定义教程") End Sub - Private Sub BtnCustomOpen_Click(sender As Object, e As EventArgs) Handles BtnCustomOpen.Click - Try - Dim CustomPath As String = Paths.Base & "PCL\Custom.xaml" - If FileUtils.Exists(CustomPath) Then - OpenExplorer(CustomPath) - Else - OpenExplorer(Paths.Base & "PCL\") - Hint("未找到 Custom.xaml,已打开 PCL 文件夹。", HintType.Blue) - End If - Catch ex As Exception - Logger.Error(ex, "打开主页文件失败") - End Try - End Sub Public Shared Sub OnMainPageTypeChanged() If FrmSetupUI Is Nothing Then Return Select Case CInt(Settings.Get(Of Integer)("UiCustomType")) @@ -354,6 +341,14 @@ Refresh: End Select FrmSetupUI.CardCustom.TriggerForceResize() End Sub + Private Sub BtnCustomFolder_Click(sender As Object, e As MouseButtonEventArgs) Handles BtnCustomFolder.Click + Dim CustomPath As String = Paths.Base & "PCL\Custom.xaml" + If FileUtils.Exists(CustomPath) Then + OpenExplorer(CustomPath) + Else + OpenExplorer(Paths.Base & "PCL\") + End If + End Sub '主题 Private Sub LabLauncherTheme5Unlock_MouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs) Handles LabLauncherTheme5Unlock.MouseLeftButtonUp @@ -458,6 +453,10 @@ Refresh: If FrmSetupUI IsNot Nothing Then FrmSetupUI.CardSwitch.Visibility = If(Not HiddenForceShow AndAlso Settings.Get(Of Boolean)("UiHiddenFunctionHidden"), Visibility.Collapsed, Visibility.Visible) End If + '版本设置子页面 + If FrmInstanceLeft IsNot Nothing Then + FrmInstanceLeft.ItemModify.Visibility = If(Not HiddenForceShow AndAlso Settings.Get(Of Boolean)("UiHiddenFunctionInstanceModify"), Visibility.Collapsed, Visibility.Visible) + End If '设置子页面 If FrmSetupLeft IsNot Nothing Then FrmSetupLeft.ItemLaunch.Visibility = If(Not HiddenForceShow AndAlso Settings.Get(Of Boolean)("UiHiddenSetupLaunch"), Visibility.Collapsed, Visibility.Visible) diff --git a/Plain Craft Launcher 2/Pages/PageSetup/Settings.vb b/Plain Craft Launcher 2/Pages/PageSetup/Settings.vb index 79501af..269c42c 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/Settings.vb +++ b/Plain Craft Launcher 2/Pages/PageSetup/Settings.vb @@ -158,6 +158,7 @@ Public Class Settings New Setting("UiHiddenPageOther", False, OnChanged:=AddressOf PageSetupUI.HiddenRefresh), New Setting("UiHiddenFunctionSelect", False, OnChanged:=AddressOf PageSetupUI.HiddenRefresh), New Setting("UiHiddenFunctionModUpdate", False, OnChanged:=AddressOf PageSetupUI.HiddenRefresh), + New Setting("UiHiddenFunctionInstanceModify", False, OnChanged:=AddressOf PageSetupUI.HiddenRefresh), New Setting("UiHiddenFunctionHidden", False, OnChanged:=AddressOf PageSetupUI.HiddenRefresh), New Setting("UiHiddenSetupLaunch", False, OnChanged:=AddressOf PageSetupUI.HiddenRefresh), New Setting("UiHiddenSetupUi", False, OnChanged:=AddressOf PageSetupUI.HiddenRefresh), diff --git a/Plain Craft Launcher 2/Pages/PageSpeedLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageSpeedLeft.xaml.vb index af4485f..9d25d25 100644 --- a/Plain Craft Launcher 2/Pages/PageSpeedLeft.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSpeedLeft.xaml.vb @@ -93,7 +93,8 @@ Public Class PageSpeedLeft Tb.Text = Loader.Error.GetDisplay(True) AddHandler Tb.MouseLeftButtonDown, Sub(sender As TextBlock, e As EventArgs) - ClipboardSet(sender.Text, SuccessHint:="已复制错误详情!") + ClipboardSet(sender.Text, False) + Hint("已复制错误详情!", HintType.Green) End Sub Card.Children.Add(Tb) #End Region diff --git a/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj b/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj index 8dbb5cb..65b544b 100644 --- a/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj +++ b/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj @@ -282,6 +282,7 @@ PageDownloadInstall.xaml + PageDownloadCompDetail.xaml diff --git a/Plain Craft Launcher 2/Resources/Help.zip b/Plain Craft Launcher 2/Resources/Help.zip index f21c776..432fd75 100644 Binary files a/Plain Craft Launcher 2/Resources/Help.zip and b/Plain Craft Launcher 2/Resources/Help.zip differ