diff --git a/Minecraft/Instance/Handler/Info/InfoMergeHandler.cs b/Minecraft/Instance/Handler/Info/InfoMergeHandler.cs index 4e553eba..af1efe8e 100644 --- a/Minecraft/Instance/Handler/Info/InfoMergeHandler.cs +++ b/Minecraft/Instance/Handler/Info/InfoMergeHandler.cs @@ -245,8 +245,13 @@ private static McVersionType RecognizeVersionType(JsonObject versionJson, DateTi return McVersionType.Fool; } - if (releaseTime.Year > 2000 && releaseTime <= new DateTime(2011, 11, 16)) { - return McVersionType.Old; + if (releaseTime.Year > 2000 && releaseTime < new DateTime(2010, 12, 03)) { + return McVersionType.OldAlpha; + } + + if (releaseTime > new DateTime(2010, 12, 03) && releaseTime < new DateTime(2011, 09, 19)) + { + return McVersionType.OldAlpha; } if (versionJson.TryGetPropertyValue("type", out var typeElement)) { diff --git a/Minecraft/Instance/Handler/InstanceBasicHandler.cs b/Minecraft/Instance/Handler/InstanceBasicHandler.cs index b34896b3..3d3c00bf 100644 --- a/Minecraft/Instance/Handler/InstanceBasicHandler.cs +++ b/Minecraft/Instance/Handler/InstanceBasicHandler.cs @@ -42,7 +42,8 @@ public static McInstanceCardType RefreshInstanceCardType(IMcInstance instance) { McVersionType.Release => McInstanceCardType.Release, McVersionType.Snapshot => McInstanceCardType.Snapshot, McVersionType.Fool => McInstanceCardType.Fool, - McVersionType.Old => McInstanceCardType.Old, + McVersionType.OldAlpha => McInstanceCardType.Old, + McVersionType.OldBeta => McInstanceCardType.Old, _ => McInstanceCardType.UnknownPatchers }; } diff --git a/Minecraft/Instance/Impl/PatchInstanceInfo.cs b/Minecraft/Instance/Impl/PatchInstanceInfo.cs index 0f9b46c3..2b437609 100644 --- a/Minecraft/Instance/Impl/PatchInstanceInfo.cs +++ b/Minecraft/Instance/Impl/PatchInstanceInfo.cs @@ -81,7 +81,8 @@ public string GetLogo() { switch (VersionType) { case McVersionType.Fool: return Basics.GetAppImagePath("Blocks/GoldBlock.png"); - case McVersionType.Old: + case McVersionType.OldAlpha: + case McVersionType.OldBeta: return Basics.GetAppImagePath("Blocks/CobbleStone.png"); case McVersionType.Snapshot: return Basics.GetAppImagePath("Blocks/CommandBlock.png"); diff --git a/Minecraft/Launch/Services/PreLaunchService.cs b/Minecraft/Launch/Services/PreLaunchService.cs index 1438b924..d6ffa38a 100644 --- a/Minecraft/Launch/Services/PreLaunchService.cs +++ b/Minecraft/Launch/Services/PreLaunchService.cs @@ -249,7 +249,8 @@ private string DetermineRequiredLanguage(string currentLang) { var mcVersionMinor = instance.InstanceInfo.McVersionMinor; var mcReleaseDate = instance.InstanceInfo.McReleaseDate; var isUnder11 = mcReleaseDate < new DateTime(2012, 1, 12) - || instance.InstanceInfo.VersionType == McVersionType.Old + || instance.InstanceInfo.VersionType == McVersionType.OldAlpha + || instance.InstanceInfo.VersionType == McVersionType.OldBeta || (mcVersionMinor == 1 && instance.InstanceInfo.McVersionBuild < 1); // For 1.0 and lower version, return "none" as no language option is available diff --git a/Minecraft/McVersion.cs b/Minecraft/McVersion.cs new file mode 100644 index 00000000..b8ba9b23 --- /dev/null +++ b/Minecraft/McVersion.cs @@ -0,0 +1,189 @@ +using System; +using System.Linq; +using System.Net.Http; +using System.Text.Json.Nodes; +using System.Threading.Tasks; +using PCL.Core.App; + +namespace PCL.Core.Minecraft; + +public enum McVersionType +{ + Snapshot, + Release, + Fool, + OldAlpha, + OldBeta, + Unknown +} + +[LifecycleService(LifecycleState.Loaded, Priority = 100)] +[LifecycleScope("mc_version", "MC 版本")] +public sealed partial class McVersion +{ +#pragma warning disable CS8618 + // Expected CS8618 + // Initialized when loaded + public static JsonNode Manifest { get; set; } +#pragma warning restore + + [LifecycleStart] + private static async Task _Start() + { + Manifest = await RefreshManifest(); + } + + [LifecycleStop] + private static void _Stop() { } + + public McVersion(string id) + { + Id = id; + JsonUrl = ""; + var manifest = Manifest; + if (manifest["versions"] is not null) + { + var versions = manifest["versions"] as JsonArray ?? []; + var current = versions.OfType().FirstOrDefault(version => (version["id"]?.ToString() ?? "") == id); + + if (current is null) + throw new FormatException(); + + JsonUrl = current["url"]?.ToString() ?? ""; + + var time = current["time"]?.GetValue().ToUniversalTime(); + var releaseTime = current["releaseTime"]?.GetValue().ToUniversalTime(); + + if (time is null || releaseTime is null) + throw new FormatException(); + + Time = (DateTime)time; + ReleaseTime = (DateTime)releaseTime; + + switch (current["type"]?.ToString() ?? "") + { + case "snapshot": + VersionType = McVersionType.Snapshot; + var idLower = id.ToLower(); + if (idLower.StartsWith("1.") + && !idLower.Contains("rc") + && !idLower.Contains("combat") + && !idLower.Contains("experimental") + && !idLower.Contains("pre")) + VersionType = McVersionType.Release; + switch (idLower) + { + case "20w14infinite": + case "20w14∞": + Id = "20w14∞"; + VersionType = McVersionType.Fool; + break; + case "3d shareware v1.34": + case "1.rv-pre1": + case "15w14a": + case "2.0": + case "22w13oneblockatatime": + case "23w13a_or_b": + case "24w14potato": + VersionType = McVersionType.Fool; + break; + default: + var releaseDate = current["releaseTime"]?.GetValue().ToUniversalTime() + .AddHours(2); + if (releaseDate is { Month: 4, Day: 1 }) + { + VersionType = McVersionType.Fool; + break; + } + + VersionType = McVersionType.Snapshot; + break; + } + + break; + case "release": + VersionType = McVersionType.Release; + break; + case "old_alpha": + VersionType = McVersionType.OldAlpha; + break; + case "old_beta": + VersionType = McVersionType.OldBeta; + break; + default: + VersionType = McVersionType.Unknown; + break; + } + + } + else + throw new FormatException(); + + return; + } + public string Id { get; } + public McVersionType VersionType { get; } + public string JsonUrl { get; } + public DateTime Time { get; } + public DateTime ReleaseTime { get; } + + public static bool operator <(McVersion first, McVersion second) + { + return first.ReleaseTime < second.ReleaseTime; + } + public static bool operator <=(McVersion first, McVersion second) + { + return first.ReleaseTime <= second.ReleaseTime; + } + + public static bool operator >(McVersion first, McVersion second) + { + return first.ReleaseTime > second.ReleaseTime; + } + public static bool operator >=(McVersion first, McVersion second) + { + return first.ReleaseTime >= second.ReleaseTime; + } + + public static bool operator ==(McVersion first, McVersion second) + { + return first.Id == second.Id; + } + public static bool operator !=(McVersion first, McVersion second) + { + return first.Id != second.Id; + } + + public async static Task RefreshManifest() + { + var client = new HttpClient(); + var getTask = await client.GetAsync("https://piston-meta.mojang.com/mc/game/version_manifest.json"); + var readTask = await getTask.Content.ReadAsStringAsync(); + return Manifest = JsonNode.Parse(readTask) ?? new JsonObject(); + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(this, obj)) + return true; + + if (ReferenceEquals(obj, null)) + return false; + + try + { + var converted = (McVersion)obj; + return Id == converted.Id; + } + catch (InvalidCastException) + { + return false; + } + } + + public override int GetHashCode() + { + return HashCode.Combine(Id, (int)VersionType); + } +} + diff --git a/Minecraft/McVersionRange.cs b/Minecraft/McVersionRange.cs new file mode 100644 index 00000000..cb46e64f --- /dev/null +++ b/Minecraft/McVersionRange.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace PCL.Core.Minecraft; + +/// +/// 版本区间(左闭右开区间) +/// +public class McVersionRange(McVersion start, McVersion end) +{ + public McVersion Start { get; } = start; + public McVersion End { get; } = end; + + public virtual bool Validate(McVersion version) + => version.ReleaseTime >= Start.ReleaseTime && version.ReleaseTime < End.ReleaseTime; + + public override string ToString() + { + return $"{Start.Id}..{End.Id}"; + } +} + +/// +/// 版本区间(闭区间) +/// +public class McVersionRangeClosed(McVersion start, McVersion end) : McVersionRange(start, end) +{ + public override bool Validate(McVersion version) + => version.ReleaseTime >= Start.ReleaseTime && version.ReleaseTime <= End.ReleaseTime; + + public override string ToString() + { + if (Start.Id == End.Id) + return $"{Start.Id}"; + return $"{Start.Id}..={End.Id}"; + } +} + + +public class McVersionRanges(IList ranges) +{ + public List RangeList { get; set; } = [..ranges]; + + public bool Validate(McVersion version) + => RangeList.Any(range => range.Validate(version)); + + /// + /// 解析文本形式的版本区间。 + /// + /// + /// 代表版本范围的字符串。
+ /// 使用规范(多个范围以半角逗号分隔):
+ /// "A..B" => [A, B)
+ /// "A..=B" => [A, B]
+ /// "A" => {A}
+ /// + /// + public static McVersionRanges Parse(string versionRange) + { + IList ranges = []; + versionRange = versionRange.Replace(" ", ""); + foreach (var str in versionRange.Split(",")) + { + if (str.Contains("..=")) + ranges.Add(new McVersionRangeClosed( + new McVersion(str.Split("..=")[0]), + new McVersion(str.Split("..=")[1]) + )); + else if (str.Contains("..")) + ranges.Add(new McVersionRange( + new McVersion(str.Split("..=")[0]), + new McVersion(str.Split("..=")[1]) + )); + else + ranges.Add(new McVersionRangeClosed(new McVersion(str), new McVersion(str))); + } + return new McVersionRanges(ranges); + } + + public override string ToString() + { + var str = RangeList.Aggregate("", (current, range) => current + $", {range}"); + return str[2..]; + } +} \ No newline at end of file diff --git a/Minecraft/ModLoader/ModLoader.cs b/Minecraft/ModLoader/ModLoader.cs new file mode 100644 index 00000000..c175eb53 --- /dev/null +++ b/Minecraft/ModLoader/ModLoader.cs @@ -0,0 +1,18 @@ +using System.Text.Json.Nodes; + +namespace PCL.Core.Minecraft.ModLoader; + +public abstract class ModLoader +{ + public abstract McVersionRanges VersionRanges { get; } + public abstract string Name { get; } + public abstract string Version { get; } + public abstract string Icon { get; } + + public virtual void MergeJson(ref JsonNode node) { } + public virtual void MergeVersionName(ref string name) { } + + public virtual void PreInstallTask() { } + public virtual void PostInstallTask() { } + public virtual void UninstallTask(ref JsonNode node, ref string name) { } +} \ No newline at end of file