From 528e587a1884a6e914f3e72222ba5cfd9e8fd1aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=84=E5=A5=9A=E6=A2=A6=E7=81=B5?= Date: Wed, 10 Sep 2025 05:53:09 +0000 Subject: [PATCH 1/8] =?UTF-8?q?feat(minecraft):=20=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E7=89=88=E6=9C=AC=20Json=20&=20=E7=89=88=E6=9C=AC=E8=8E=B7?= =?UTF-8?q?=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Minecraft/Instance/Client.cs | 116 +++++++++++++++++++++++++++++++++ PCL.Core.sln | 30 +++++++++ Utils/OS/EnvironmentInterop.cs | 1 + 3 files changed, 147 insertions(+) create mode 100644 Minecraft/Instance/Client.cs create mode 100644 PCL.Core.sln diff --git a/Minecraft/Instance/Client.cs b/Minecraft/Instance/Client.cs new file mode 100644 index 00000000..9d841d7a --- /dev/null +++ b/Minecraft/Instance/Client.cs @@ -0,0 +1,116 @@ +using System.Text.Json.Nodes; +using System.Threading.Tasks; +using System.Net.Http; +using PCL.Core.Net; +using PCL.Core.App; +using PCL.Core.Logging; +using System.Linq; +using System.Data; +using PCL.Core.Utils.Hash; +using System.Collections.Generic; +using System.Management; +using System; +using System.Runtime.InteropServices; + +namespace PCL.Core.Minecraft.Instance; + +public static class MinecraftClient +{ + public static JsonNode? VersionList; + + private const string Official = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"; + private const string BmclApi = "https://bmclapi2.bangbang93.com/mc/game/version_manifest_v2.json"; + private static string[] _GetVersionSource() => Config.ToolConfigGroup.DownloadConfigGroup.VersionSourceSolution switch + { + 0 => [Official, Official, BmclApi], + 1 => [Official, BmclApi, Official], + 2 => [BmclApi, BmclApi, Official] + }; + private static string[] _GetFileSource(string uri) + { + var mirror = uri + .Replace("piston-meta.mojang.com", "bmclapi2.bangbang93.com") + .Replace("libraries.minecraft.net", "bmclapi2.bangbang93.com/maven") + .Replace("pistom-data.mojang.com", "bmclapi2.bangbang93.com"); + return Config.ToolConfigGroup.DownloadConfigGroup.VersionSourceSolution switch + { + 0 => [uri, uri, mirror], + 1 => [uri, mirror, uri], + 2 => [mirror, mirror, uri] + }; + } + + public static async Task GetVersionInfoAsync(string mcVersion) + { + if (VersionList is null) await UpdateVersionIndexAsync(); + return VersionList!["versions"]?.AsArray().Where(value => value?["id"]?.ToString() == mcVersion).First(); + } + public static async Task UpdateVersionIndexAsync() + { + foreach (var source in _GetVersionSource()) + { + try + { + using var handler = await HttpRequestBuilder.Create(source, HttpMethod.Get).SendAsync(true); + VersionList = await handler.AsJsonAsync(); + } + catch (HttpRequestException ex) + { + LogWrapper.Error(ex, "Minecraft", "Failed to get version list"); + } + } + } + public static async Task DownloadJsonAsync(string mcVersion, string? exceptHash = null) + { + var version = await GetVersionInfoAsync(mcVersion); + if (version is null) throw new VersionNotFoundException($"Version not found: {mcVersion}"); + foreach (var source in _GetFileSource(version["url"]!.ToString())) + { + try + { + var response = await HttpRequestBuilder.Create(source, HttpMethod.Get).SendAsync(true); + var content = await response.AsStringAsync(); + if (!string.IsNullOrEmpty(exceptHash)) + { + var hashResult = SHA1Provider.Instance.ComputeHash(content); + if (hashResult != exceptHash) continue; + } + return content; + } + catch (HttpRequestException ex) + { + LogWrapper.Error(ex, "Minecraft", "下载版本 Json 失败"); + } + } + throw new HttpRequestException("Failed to download version json:All of source unavailable"); + } + public static async Task> AnalysisLibrary(JsonNode versionJson) + { + var list = new List(); + foreach (var library in versionJson["libraries"]!.AsArray()) + { + var artifact = library?["downloads"]?["artifact"]; + var classifiers = library?["downloads"]?["classifiers"]; + if (artifact is not null) list.Add(artifact); + if (classifiers is not null) + { + var rules = library?["rules"]; + var nativeKey = library?["natives"]?[Environment.OSVersion.Platform.ToString()]?.ToString(); + if (string.IsNullOrEmpty(nativeKey)) continue; + foreach (var rule in rules!.AsArray()) + { + if (rule!["action"]!.ToString() == "disallow") + { + var os = rule["os"]; + var osName = os!["name"]?.ToString(); + var arch = os!["arch"]?.ToString(); + if (!string.IsNullOrEmpty(osName) && + RuntimeInformation.IsOSPlatform(OSPlatform.Create(osName.ToUpper()))) continue; + + } + } + } + } + } + private +} \ No newline at end of file diff --git a/PCL.Core.sln b/PCL.Core.sln new file mode 100644 index 00000000..d5725591 --- /dev/null +++ b/PCL.Core.sln @@ -0,0 +1,30 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PCL.Core", "PCL.Core.csproj", "{F46883D6-36B8-D972-8661-D0CA2DB6695B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PCL.Core.SourceGenerators", "SourceGenerators\PCL.Core.SourceGenerators.csproj", "{DDC9C895-B9B2-EBE5-6554-90C9CB5D30E5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F46883D6-36B8-D972-8661-D0CA2DB6695B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F46883D6-36B8-D972-8661-D0CA2DB6695B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F46883D6-36B8-D972-8661-D0CA2DB6695B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F46883D6-36B8-D972-8661-D0CA2DB6695B}.Release|Any CPU.Build.0 = Release|Any CPU + {DDC9C895-B9B2-EBE5-6554-90C9CB5D30E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DDC9C895-B9B2-EBE5-6554-90C9CB5D30E5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DDC9C895-B9B2-EBE5-6554-90C9CB5D30E5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DDC9C895-B9B2-EBE5-6554-90C9CB5D30E5}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {60FF8920-4AD0-4C90-8A77-0265F516FB85} + EndGlobalSection +EndGlobal diff --git a/Utils/OS/EnvironmentInterop.cs b/Utils/OS/EnvironmentInterop.cs index db6a31f5..8c71549a 100644 --- a/Utils/OS/EnvironmentInterop.cs +++ b/Utils/OS/EnvironmentInterop.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.InteropServices; using PCL.Core.Logging; using PCL.Core.Utils.Exts; From 5fd5b7cb7bee2e7dc640d0687a89ee058d1e1bf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=84=E5=A5=9A=E6=A2=A6=E7=81=B5?= Date: Wed, 10 Sep 2025 14:45:32 +0000 Subject: [PATCH 2/8] =?UTF-8?q?imp(minecraft):=20=E6=94=B9=E8=BF=9B?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=BA=93=E9=80=89=E6=8B=A9=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Minecraft/Instance/Client.cs | 53 ++++++++++++++++++++++++----------- Utils/Exts/StringExtension.cs | 10 +++++-- 2 files changed, 44 insertions(+), 19 deletions(-) diff --git a/Minecraft/Instance/Client.cs b/Minecraft/Instance/Client.cs index 9d841d7a..2cb68448 100644 --- a/Minecraft/Instance/Client.cs +++ b/Minecraft/Instance/Client.cs @@ -11,6 +11,7 @@ using System.Management; using System; using System.Runtime.InteropServices; +using PCL.Core.Utils.Exts; namespace PCL.Core.Minecraft.Instance; @@ -84,33 +85,51 @@ public static async Task DownloadJsonAsync(string mcVersion, string? exc } throw new HttpRequestException("Failed to download version json:All of source unavailable"); } - public static async Task> AnalysisLibrary(JsonNode versionJson) + public static async List AnalysisLibrary(JsonNode versionJson) { - var list = new List(); + var list = new List(); foreach (var library in versionJson["libraries"]!.AsArray()) { + var rules = library?["rules"]; + // skip check when rules is null + if (rules is not null) foreach (var rule in rules.AsArray()) + { + // do nothing when allow/disallow (it skipped by continue) + switch (rule!["action"]!.ToString()) + { + case "disallow": + var os = rule["os"]; + var osName = os!["name"]?.ToString(); + var arch = os!["arch"]?.ToString(); + if (!string.IsNullOrEmpty(osName) && + RuntimeInformation.IsOSPlatform(OSPlatform.Create(osName.ToUpper()))) continue; + var currentArchitecture = Architecture.X86; + + if (!Enum.TryParse(arch!.Capitalize(), out currentArchitecture)) continue; + if (!string.IsNullOrEmpty(arch) && + RuntimeInformation.OSArchitecture == currentArchitecture) continue; + break; + case "allow": + default: + break; + } + } var artifact = library?["downloads"]?["artifact"]; var classifiers = library?["downloads"]?["classifiers"]; - if (artifact is not null) list.Add(artifact); + if (artifact is not null) + { + // list.Add(new DownloadItem()) + } if (classifiers is not null) { - var rules = library?["rules"]; + // get key by os type var nativeKey = library?["natives"]?[Environment.OSVersion.Platform.ToString()]?.ToString(); if (string.IsNullOrEmpty(nativeKey)) continue; - foreach (var rule in rules!.AsArray()) - { - if (rule!["action"]!.ToString() == "disallow") - { - var os = rule["os"]; - var osName = os!["name"]?.ToString(); - var arch = os!["arch"]?.ToString(); - if (!string.IsNullOrEmpty(osName) && - RuntimeInformation.IsOSPlatform(OSPlatform.Create(osName.ToUpper()))) continue; - - } - } + if (nativeKey.Contains("arch")) + nativeKey = nativeKey.Replace("${arch}", $"{(RuntimeInformation.OSArchitecture == Architecture.X86 ? "86" : "64")}"); + } + } } - private } \ No newline at end of file diff --git a/Utils/Exts/StringExtension.cs b/Utils/Exts/StringExtension.cs index 701464cb..9d3d7159 100644 --- a/Utils/Exts/StringExtension.cs +++ b/Utils/Exts/StringExtension.cs @@ -7,6 +7,7 @@ using System.Numerics; using System.Reflection; using System.Text.RegularExpressions; +using System.Threading; namespace PCL.Core.Utils.Exts; @@ -41,7 +42,7 @@ public static class StringExtension if (targetType.IsEnum) return Enum.Parse(targetType, value, ignoreCase: true); - var parse = targetType.GetMethod("Parse", + var parse = targetType.GetMethod("Parse", BindingFlags.Public | BindingFlags.Static, binder: null, types: [typeof(string)], modifiers: null); if (parse is not null) return parse.Invoke(null, [value]); @@ -203,7 +204,7 @@ public static bool IsASCII(this string str) { return str.All(c => c < 128); } - + public static bool StartsWithF(this string str, string prefix, bool ignoreCase = false) => str.StartsWith(prefix, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal); @@ -224,4 +225,9 @@ public static int LastIndexOfF(this string str, string subStr, bool ignoreCase = public static int LastIndexOfF(this string str, string subStr, int startIndex, bool ignoreCase = false) => str.LastIndexOf(subStr, startIndex, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal); + + public static string Capitalize(this string text) + => Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(text); + + } From fe6fb0bb72faffc92eaba0cc2b11bbf911e4b197 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=84=E5=A5=9A=E6=A2=A6=E7=81=B5?= Date: Thu, 11 Sep 2025 05:52:53 +0000 Subject: [PATCH 3/8] =?UTF-8?q?impl(IO):=20=E5=AE=9E=E7=8E=B0=E4=B8=80?= =?UTF-8?q?=E4=B8=AA=20NetFile=20=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IO/NetFile.cs | 39 +++++++++++++++++++ .../{Client.cs => Clients/MinecraftClient.cs} | 16 ++++---- Minecraft/Instance/IClient.cs | 14 +++++++ Minecraft/Instance/IVersion.cs | 1 + NOTICE | 16 -------- PCL.Core.sln | 2 +- 6 files changed, 63 insertions(+), 25 deletions(-) create mode 100644 IO/NetFile.cs rename Minecraft/Instance/{Client.cs => Clients/MinecraftClient.cs} (93%) create mode 100644 Minecraft/Instance/IClient.cs create mode 100644 Minecraft/Instance/IVersion.cs delete mode 100644 NOTICE diff --git a/IO/NetFile.cs b/IO/NetFile.cs new file mode 100644 index 00000000..a05f1e26 --- /dev/null +++ b/IO/NetFile.cs @@ -0,0 +1,39 @@ +using System; +using System.IO; +using PCL.Core.Utils.Hash; + +namespace PCL.Core.IO; + +public class NetFile +{ + public required string Path { get; set; } + public int Size = -1; + public HashAlgorithm Algorithm = HashAlgorithm.sha1; + public string Hash = ""; + + public bool CheckFile() + { + if (!File.Exists(Path)) return false; + if (!string.IsNullOrEmpty(Hash)) + { + using var fs = new FileStream(Path, FileMode.Open, FileAccess.Read, FileShare.Read, 16384, true); + var hash = Algorithm switch + { + HashAlgorithm.md5 => MD5Provider.Instance.ComputeHash(fs), + HashAlgorithm.sha1 => SHA1Provider.Instance.ComputeHash(fs), + HashAlgorithm.sha256 => SHA256Provider.Instance.ComputeHash(fs), + HashAlgorithm.sha512 => SHA512Provider.Instance.ComputeHash(fs), + _ => throw new NotSupportedException($"Unsupport algorithm: {Algorithm}") + }; + return hash == Hash; + } + return true; + } +} + +public enum HashAlgorithm { + md5, + sha1, + sha256, + sha512 +} \ No newline at end of file diff --git a/Minecraft/Instance/Client.cs b/Minecraft/Instance/Clients/MinecraftClient.cs similarity index 93% rename from Minecraft/Instance/Client.cs rename to Minecraft/Instance/Clients/MinecraftClient.cs index 2cb68448..05aca59e 100644 --- a/Minecraft/Instance/Client.cs +++ b/Minecraft/Instance/Clients/MinecraftClient.cs @@ -12,13 +12,13 @@ using System; using System.Runtime.InteropServices; using PCL.Core.Utils.Exts; +using PCL.Core.Minecraft.Instance; -namespace PCL.Core.Minecraft.Instance; +namespace PCL.Core.Minecraft.Instance.Clients; -public static class MinecraftClient +public class MinecraftClient : IClient { public static JsonNode? VersionList; - private const string Official = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"; private const string BmclApi = "https://bmclapi2.bangbang93.com/mc/game/version_manifest_v2.json"; private static string[] _GetVersionSource() => Config.ToolConfigGroup.DownloadConfigGroup.VersionSourceSolution switch @@ -61,7 +61,7 @@ public static async Task UpdateVersionIndexAsync() } } } - public static async Task DownloadJsonAsync(string mcVersion, string? exceptHash = null) + public static async Task GetJsonAsync(string mcVersion, string? exceptHash = null) { var version = await GetVersionInfoAsync(mcVersion); if (version is null) throw new VersionNotFoundException($"Version not found: {mcVersion}"); @@ -85,7 +85,7 @@ public static async Task DownloadJsonAsync(string mcVersion, string? exc } throw new HttpRequestException("Failed to download version json:All of source unavailable"); } - public static async List AnalysisLibrary(JsonNode versionJson) + public static List AnalysisLibrary(JsonNode versionJson) { var list = new List(); foreach (var library in versionJson["libraries"]!.AsArray()) @@ -104,7 +104,7 @@ public static async List AnalysisLibrary(JsonNode versionJson) if (!string.IsNullOrEmpty(osName) && RuntimeInformation.IsOSPlatform(OSPlatform.Create(osName.ToUpper()))) continue; var currentArchitecture = Architecture.X86; - + if (!Enum.TryParse(arch!.Capitalize(), out currentArchitecture)) continue; if (!string.IsNullOrEmpty(arch) && RuntimeInformation.OSArchitecture == currentArchitecture) continue; @@ -118,7 +118,7 @@ public static async List AnalysisLibrary(JsonNode versionJson) var classifiers = library?["downloads"]?["classifiers"]; if (artifact is not null) { - // list.Add(new DownloadItem()) + list.Add(new DownloadItem()) } if (classifiers is not null) { @@ -129,7 +129,7 @@ public static async List AnalysisLibrary(JsonNode versionJson) nativeKey = nativeKey.Replace("${arch}", $"{(RuntimeInformation.OSArchitecture == Architecture.X86 ? "86" : "64")}"); } - } + return list; } } \ No newline at end of file diff --git a/Minecraft/Instance/IClient.cs b/Minecraft/Instance/IClient.cs new file mode 100644 index 00000000..3d436371 --- /dev/null +++ b/Minecraft/Instance/IClient.cs @@ -0,0 +1,14 @@ +using System.Threading.Tasks; +using PCL.Core.Net; +using System.Text.Json.Nodes; +using System.Collections.Generic; + +namespace PCL.Core.Minecraft.Instance; + +public interface IClient +{ + static abstract Task GetVersionInfoAsync(string version); + static abstract Task UpdateVersionIndexAsync(); + static abstract List AnalysisLibrary(JsonNode versionJson); + static abstract Task GetJsonAsync(string version,string exceptHash); +} diff --git a/Minecraft/Instance/IVersion.cs b/Minecraft/Instance/IVersion.cs new file mode 100644 index 00000000..39770628 --- /dev/null +++ b/Minecraft/Instance/IVersion.cs @@ -0,0 +1 @@ +namespace PCL.Core.Minecraft.Instance.Clients; \ No newline at end of file diff --git a/NOTICE b/NOTICE deleted file mode 100644 index 46439a0c..00000000 --- a/NOTICE +++ /dev/null @@ -1,16 +0,0 @@ -This product uses the following open source components: - -- SharpZipLib (MIT License) - https://github.com/icsharpcode/SharpZipLib - -- LiteDB (MIT License) - https://github.com/litedb-org/LiteDB - -- Microsoft.Net.Compilers.Toolset (MIT License) - https://github.com/dotnet/roslyn - -- Microsoft.Toolkit.Uwp.Notifications (MIT License) - https://github.com/CommunityToolkit/WindowsCommunityToolkit - -- PolySharp (MIT License) - https://github.com/Sergio0694/PolySharp/ \ No newline at end of file diff --git a/PCL.Core.sln b/PCL.Core.sln index d5725591..551e8264 100644 --- a/PCL.Core.sln +++ b/PCL.Core.sln @@ -27,4 +27,4 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {60FF8920-4AD0-4C90-8A77-0265F516FB85} EndGlobalSection -EndGlobal +EndGlobal \ No newline at end of file From 7841e4eb9cb9d4c9acd7f53b21c1ceea904be11f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=84=E5=A5=9A=E6=A2=A6=E7=81=B5?= Date: Thu, 11 Sep 2025 14:41:18 +0000 Subject: [PATCH 4/8] =?UTF-8?q?feat(instance):=20=E5=88=9D=E6=AD=A5?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E5=AE=89=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IO/NetFile.cs | 22 +++++++++++- Minecraft/Instance/Clients/MinecraftClient.cs | 36 +++++++++++++++++-- Minecraft/Instance/IClient.cs | 1 + Utils/Exts/StringExtension.cs | 2 +- 4 files changed, 56 insertions(+), 5 deletions(-) diff --git a/IO/NetFile.cs b/IO/NetFile.cs index a05f1e26..08a6e361 100644 --- a/IO/NetFile.cs +++ b/IO/NetFile.cs @@ -1,5 +1,8 @@ using System; +using System.Collections.Generic; using System.IO; +using PCL.Core.Net; +using PCL.Core.Utils.Exts; using PCL.Core.Utils.Hash; namespace PCL.Core.IO; @@ -10,7 +13,8 @@ public class NetFile public int Size = -1; public HashAlgorithm Algorithm = HashAlgorithm.sha1; public string Hash = ""; - + public required string[] Url { get; set; } + public bool CheckFile() { if (!File.Exists(Path)) return false; @@ -29,6 +33,22 @@ public bool CheckFile() } return true; } + // 这个方法存在的意义就是为了让 Downloader 支持换源重试 + /// + /// 获取当前对象的 DownloadItem 列表。 + /// + /// + public List GetDownloadItem() + { + var list = new List(); + foreach (var url in Url) + { + var item = new DownloadItem(url.ToUri(), Path); + item.Finished += () => CheckFile(); + list.Add(item); + } + return list; + } } public enum HashAlgorithm { diff --git a/Minecraft/Instance/Clients/MinecraftClient.cs b/Minecraft/Instance/Clients/MinecraftClient.cs index 05aca59e..d62fbc15 100644 --- a/Minecraft/Instance/Clients/MinecraftClient.cs +++ b/Minecraft/Instance/Clients/MinecraftClient.cs @@ -13,6 +13,7 @@ using System.Runtime.InteropServices; using PCL.Core.Utils.Exts; using PCL.Core.Minecraft.Instance; +using PCL.Core.IO; namespace PCL.Core.Minecraft.Instance.Clients; @@ -71,6 +72,7 @@ public static async Task GetJsonAsync(string mcVersion, string? exceptHa { var response = await HttpRequestBuilder.Create(source, HttpMethod.Get).SendAsync(true); var content = await response.AsStringAsync(); + exceptHash ??= version["sha1"]?.ToString(); if (!string.IsNullOrEmpty(exceptHash)) { var hashResult = SHA1Provider.Instance.ComputeHash(content); @@ -85,9 +87,9 @@ public static async Task GetJsonAsync(string mcVersion, string? exceptHa } throw new HttpRequestException("Failed to download version json:All of source unavailable"); } - public static List AnalysisLibrary(JsonNode versionJson) + public static List AnalysisLibrary(JsonNode versionJson) { - var list = new List(); + var list = new List(); foreach (var library in versionJson["libraries"]!.AsArray()) { var rules = library?["rules"]; @@ -118,7 +120,14 @@ public static List AnalysisLibrary(JsonNode versionJson) var classifiers = library?["downloads"]?["classifiers"]; if (artifact is not null) { - list.Add(new DownloadItem()) + list.Add(new NetFile() + { + Path = "", + Url = [""], + Size = 0, + Algorithm = HashAlgorithm.sha1, + Hash = "" + }); } if (classifiers is not null) { @@ -132,4 +141,25 @@ public static List AnalysisLibrary(JsonNode versionJson) } return list; } + public static async Task StartClientInstallAsync(string mcVersion, string path) + { + var versionJson = await GetJsonAsync(mcVersion); + var versionJsonNode = JsonNode.Parse(versionJson); + ArgumentNullException.ThrowIfNull(versionJsonNode); + var libraryList = AnalysisLibrary(versionJsonNode); + var downloader = new Downloader(); + foreach (var library in libraryList) + { + foreach (var item in library.GetDownloadItem()) + { + downloader.AddItem(item); + } + } + downloader.Start(); + + } + public static async Task?> AnalysisAssets(JsonNode versionJson) + { + return null; + } } \ No newline at end of file diff --git a/Minecraft/Instance/IClient.cs b/Minecraft/Instance/IClient.cs index 3d436371..56b5556a 100644 --- a/Minecraft/Instance/IClient.cs +++ b/Minecraft/Instance/IClient.cs @@ -11,4 +11,5 @@ public interface IClient static abstract Task UpdateVersionIndexAsync(); static abstract List AnalysisLibrary(JsonNode versionJson); static abstract Task GetJsonAsync(string version,string exceptHash); + static abstract Task StartClientInstallAsync(string version,string path); } diff --git a/Utils/Exts/StringExtension.cs b/Utils/Exts/StringExtension.cs index 9d3d7159..9c7c419b 100644 --- a/Utils/Exts/StringExtension.cs +++ b/Utils/Exts/StringExtension.cs @@ -228,6 +228,6 @@ public static int LastIndexOfF(this string str, string subStr, int startIndex, b public static string Capitalize(this string text) => Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(text); - + public static Uri ToUri(this string url) => new Uri(url); } From 2b14c6492a0aadd7d5a4cf7a8f0bfaa7e8f73ade Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=84=E5=A5=9A=E6=A2=A6=E7=81=B5?= Date: Fri, 12 Sep 2025 09:50:56 +0800 Subject: [PATCH 5/8] chore(instance): add ignore rule --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 6a35cb36..ce42e922 100644 --- a/.gitignore +++ b/.gitignore @@ -408,3 +408,7 @@ FodyWeavers.xsd # Generated Code **/*.g.cs + +# Duplicate Project File + +PCL.Core.sln \ No newline at end of file From c745ddb361296e5ac9d94fd6d01aea6907a8f924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=84=E5=A5=9A=E6=A2=A6=E7=81=B5?= Date: Fri, 12 Sep 2025 04:24:08 +0000 Subject: [PATCH 6/8] chore:sync --- Minecraft/Instance/Clients/MinecraftClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Minecraft/Instance/Clients/MinecraftClient.cs b/Minecraft/Instance/Clients/MinecraftClient.cs index d62fbc15..7d04e6d3 100644 --- a/Minecraft/Instance/Clients/MinecraftClient.cs +++ b/Minecraft/Instance/Clients/MinecraftClient.cs @@ -160,6 +160,6 @@ public static async Task StartClientInstallAsync(string mcVersion, string path) } public static async Task?> AnalysisAssets(JsonNode versionJson) { - return null; + } } \ No newline at end of file From 9d3a47312b950a07de6e95efbdd22f1679c3ffbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=84=E5=A5=9A=E6=A2=A6=E7=81=B5?= Date: Fri, 12 Sep 2025 05:54:13 +0000 Subject: [PATCH 7/8] =?UTF-8?q?feat(instance):=20=E5=88=9D=E6=AD=A5?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=20Assets=20=E5=88=86=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 --- Minecraft/Instance/Clients/MinecraftClient.cs | 22 ++++++++++---- PCL.Core.sln | 30 ------------------- PCL.Core.slnx | 2 +- 4 files changed, 18 insertions(+), 40 deletions(-) delete mode 100644 PCL.Core.sln diff --git a/.gitignore b/.gitignore index ce42e922..6a35cb36 100644 --- a/.gitignore +++ b/.gitignore @@ -408,7 +408,3 @@ FodyWeavers.xsd # Generated Code **/*.g.cs - -# Duplicate Project File - -PCL.Core.sln \ No newline at end of file diff --git a/Minecraft/Instance/Clients/MinecraftClient.cs b/Minecraft/Instance/Clients/MinecraftClient.cs index 7d04e6d3..096f32ab 100644 --- a/Minecraft/Instance/Clients/MinecraftClient.cs +++ b/Minecraft/Instance/Clients/MinecraftClient.cs @@ -22,6 +22,7 @@ public class MinecraftClient : IClient public static JsonNode? VersionList; private const string Official = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"; private const string BmclApi = "https://bmclapi2.bangbang93.com/mc/game/version_manifest_v2.json"; + private const string AssetsBaseUri = "https://resources.download.minecraft.net"; private static string[] _GetVersionSource() => Config.ToolConfigGroup.DownloadConfigGroup.VersionSourceSolution switch { 0 => [Official, Official, BmclApi], @@ -33,7 +34,8 @@ private static string[] _GetFileSource(string uri) var mirror = uri .Replace("piston-meta.mojang.com", "bmclapi2.bangbang93.com") .Replace("libraries.minecraft.net", "bmclapi2.bangbang93.com/maven") - .Replace("pistom-data.mojang.com", "bmclapi2.bangbang93.com"); + .Replace("pistom-data.mojang.com", "bmclapi2.bangbang93.com") + .Replace(AssetsBaseUri,"https://bmclapi2.bangbang93.com/assets"); return Config.ToolConfigGroup.DownloadConfigGroup.VersionSourceSolution switch { 0 => [uri, uri, mirror], @@ -141,6 +143,19 @@ public static List AnalysisLibrary(JsonNode versionJson) } return list; } + public static async Task?> AnalysisAssets(JsonNode versionJson) + { + var list = new List(); + foreach (var asset in versionJson["object"]) + { + var hash = asset["hash"].ToString(); + var size = asset["size"].GetValue(); + var pathSuffix = $"{hash.SubString(0, 1)}/{hash}"; + var assetUri = $"{AssetsBaseUri}/{pathSuffix}"; + var path = $"assets/objects/{pathSuffix}"; + + } + } public static async Task StartClientInstallAsync(string mcVersion, string path) { var versionJson = await GetJsonAsync(mcVersion); @@ -158,8 +173,5 @@ public static async Task StartClientInstallAsync(string mcVersion, string path) downloader.Start(); } - public static async Task?> AnalysisAssets(JsonNode versionJson) - { - - } + } \ No newline at end of file diff --git a/PCL.Core.sln b/PCL.Core.sln deleted file mode 100644 index 551e8264..00000000 --- a/PCL.Core.sln +++ /dev/null @@ -1,30 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.5.2.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PCL.Core", "PCL.Core.csproj", "{F46883D6-36B8-D972-8661-D0CA2DB6695B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PCL.Core.SourceGenerators", "SourceGenerators\PCL.Core.SourceGenerators.csproj", "{DDC9C895-B9B2-EBE5-6554-90C9CB5D30E5}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {F46883D6-36B8-D972-8661-D0CA2DB6695B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F46883D6-36B8-D972-8661-D0CA2DB6695B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F46883D6-36B8-D972-8661-D0CA2DB6695B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F46883D6-36B8-D972-8661-D0CA2DB6695B}.Release|Any CPU.Build.0 = Release|Any CPU - {DDC9C895-B9B2-EBE5-6554-90C9CB5D30E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DDC9C895-B9B2-EBE5-6554-90C9CB5D30E5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DDC9C895-B9B2-EBE5-6554-90C9CB5D30E5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DDC9C895-B9B2-EBE5-6554-90C9CB5D30E5}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {60FF8920-4AD0-4C90-8A77-0265F516FB85} - EndGlobalSection -EndGlobal \ No newline at end of file diff --git a/PCL.Core.slnx b/PCL.Core.slnx index 65157cbf..fc059954 100644 --- a/PCL.Core.slnx +++ b/PCL.Core.slnx @@ -1,4 +1,4 @@ - + From 0168cb487f0db88b618a147c590a754f55dc168a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=84=E5=A5=9A=E6=A2=A6=E7=81=B5?= Date: Wed, 24 Sep 2025 15:38:11 +0800 Subject: [PATCH 8/8] =?UTF-8?q?refactor(instance):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E9=83=A8=E5=88=86=E6=96=B9=E6=B3=95=20&=20feat(instance):=20?= =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=AE=9E=E7=8E=B0=20Forge=20=E5=AE=89?= =?UTF-8?q?=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Minecraft/Instance/Clients/ClientBase.cs | 46 +++++++++++ Minecraft/Instance/Clients/ForgeClient.cs | 6 ++ Minecraft/Instance/Clients/MinecraftClient.cs | 82 +++++++++++-------- Minecraft/Instance/IClient.cs | 9 +- Minecraft/Instance/InstanceInstallHandler.cs | 17 ++++ Minecraft/JavaHelper.cs | 43 ++++++++++ 6 files changed, 166 insertions(+), 37 deletions(-) create mode 100644 Minecraft/Instance/Clients/ClientBase.cs create mode 100644 Minecraft/Instance/Clients/ForgeClient.cs create mode 100644 Minecraft/Instance/InstanceInstallHandler.cs create mode 100644 Minecraft/JavaHelper.cs diff --git a/Minecraft/Instance/Clients/ClientBase.cs b/Minecraft/Instance/Clients/ClientBase.cs new file mode 100644 index 00000000..1005587f --- /dev/null +++ b/Minecraft/Instance/Clients/ClientBase.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Nodes; +using System.Threading.Tasks; +using PCL.Core.IO; + +namespace PCL.Core.Minecraft.Instance.Clients; + +public class ClientBase : IClient +{ + + public static Task GetVersionInfoAsync(string version) + { + throw new NotImplementedException(); + } + + public static Task ParseAsync(string version) + { + throw new NotImplementedException(); + } + + public static Task UpdateVersionIndexAsync() + { + throw new NotImplementedException(); + } + + public virtual Task> AnalyzeLibraryAsync() + { + throw new NotImplementedException(); + } + + public virtual Task> AnalyzeMissingLibraryAsync() + { + throw new NotImplementedException(); + } + + public virtual Task ExecuteInstallerAsync(string path) + { + throw new NotImplementedException(); + } + + public virtual Task GetJsonAsync() + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/Minecraft/Instance/Clients/ForgeClient.cs b/Minecraft/Instance/Clients/ForgeClient.cs new file mode 100644 index 00000000..3b54ec5b --- /dev/null +++ b/Minecraft/Instance/Clients/ForgeClient.cs @@ -0,0 +1,6 @@ +namespace PCL.Core.Minecraft.Instance.Clients; + +public class ForgeClient : ClientBase +{ + +} \ No newline at end of file diff --git a/Minecraft/Instance/Clients/MinecraftClient.cs b/Minecraft/Instance/Clients/MinecraftClient.cs index 096f32ab..47f903c6 100644 --- a/Minecraft/Instance/Clients/MinecraftClient.cs +++ b/Minecraft/Instance/Clients/MinecraftClient.cs @@ -17,9 +17,13 @@ namespace PCL.Core.Minecraft.Instance.Clients; -public class MinecraftClient : IClient +public class MinecraftClient : ClientBase { public static JsonNode? VersionList; + private Version? _version; + private JsonNode? _versionJson; + private string? _jsonUrl; + private string? _jsonHash; private const string Official = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"; private const string BmclApi = "https://bmclapi2.bangbang93.com/mc/game/version_manifest_v2.json"; private const string AssetsBaseUri = "https://resources.download.minecraft.net"; @@ -35,7 +39,7 @@ private static string[] _GetFileSource(string uri) .Replace("piston-meta.mojang.com", "bmclapi2.bangbang93.com") .Replace("libraries.minecraft.net", "bmclapi2.bangbang93.com/maven") .Replace("pistom-data.mojang.com", "bmclapi2.bangbang93.com") - .Replace(AssetsBaseUri,"https://bmclapi2.bangbang93.com/assets"); + .Replace(AssetsBaseUri, "https://bmclapi2.bangbang93.com/assets"); return Config.ToolConfigGroup.DownloadConfigGroup.VersionSourceSolution switch { 0 => [uri, uri, mirror], @@ -44,12 +48,12 @@ private static string[] _GetFileSource(string uri) }; } - public static async Task GetVersionInfoAsync(string mcVersion) + public new static async Task GetVersionInfoAsync(string mcVersion) { if (VersionList is null) await UpdateVersionIndexAsync(); return VersionList!["versions"]?.AsArray().Where(value => value?["id"]?.ToString() == mcVersion).First(); } - public static async Task UpdateVersionIndexAsync() + public new static async Task UpdateVersionIndexAsync() { foreach (var source in _GetVersionSource()) { @@ -64,21 +68,19 @@ public static async Task UpdateVersionIndexAsync() } } } - public static async Task GetJsonAsync(string mcVersion, string? exceptHash = null) + public override async Task GetJsonAsync() { - var version = await GetVersionInfoAsync(mcVersion); - if (version is null) throw new VersionNotFoundException($"Version not found: {mcVersion}"); - foreach (var source in _GetFileSource(version["url"]!.ToString())) + + foreach (var source in _GetFileSource(_jsonUrl!.ToString())) { try { var response = await HttpRequestBuilder.Create(source, HttpMethod.Get).SendAsync(true); var content = await response.AsStringAsync(); - exceptHash ??= version["sha1"]?.ToString(); - if (!string.IsNullOrEmpty(exceptHash)) + if (!string.IsNullOrEmpty(_jsonHash)) { var hashResult = SHA1Provider.Instance.ComputeHash(content); - if (hashResult != exceptHash) continue; + if (string.Equals(hashResult, _jsonHash, StringComparison.OrdinalIgnoreCase)) continue; } return content; } @@ -89,10 +91,14 @@ public static async Task GetJsonAsync(string mcVersion, string? exceptHa } throw new HttpRequestException("Failed to download version json:All of source unavailable"); } - public static List AnalysisLibrary(JsonNode versionJson) + public override async Task> AnalyzeLibraryAsync() { var list = new List(); - foreach (var library in versionJson["libraries"]!.AsArray()) + if (_versionJson is null) + { + _versionJson = JsonNode.Parse(await GetJsonAsync()); + } + foreach (var library in _versionJson!["libraries"]!.AsArray()) { var rules = library?["rules"]; // skip check when rules is null @@ -143,35 +149,43 @@ public static List AnalysisLibrary(JsonNode versionJson) } return list; } - public static async Task?> AnalysisAssets(JsonNode versionJson) + public override Task> AnalyzeMissingLibraryAsync() + { + return base.AnalyzeMissingLibraryAsync(); + } + public async Task> AnalyzeAssetsAsync(JsonNode versionJson) { + await GetJsonAsync(); var list = new List(); - foreach (var asset in versionJson["object"]) + foreach (var asset in versionJson["object"]!.AsObject()) { - var hash = asset["hash"].ToString(); - var size = asset["size"].GetValue(); - var pathSuffix = $"{hash.SubString(0, 1)}/{hash}"; + var hash = asset!.Value!["hash"]!.ToString(); + var size = asset!.Value!["size"]!.GetValue(); + var pathSuffix = $"{hash.Substring(0, 1)}/{hash}"; var assetUri = $"{AssetsBaseUri}/{pathSuffix}"; var path = $"assets/objects/{pathSuffix}"; - + list.Add(new NetFile() + { + Url = _GetFileSource(assetUri), + Path = path, + Algorithm = HashAlgorithm.sha1, + Hash = hash + }); } + return list; } - public static async Task StartClientInstallAsync(string mcVersion, string path) + public static async Task ParseAsync(string mcVersion) { - var versionJson = await GetJsonAsync(mcVersion); - var versionJsonNode = JsonNode.Parse(versionJson); - ArgumentNullException.ThrowIfNull(versionJsonNode); - var libraryList = AnalysisLibrary(versionJsonNode); - var downloader = new Downloader(); - foreach (var library in libraryList) + if (VersionList is null) await UpdateVersionIndexAsync(); + var info = GetVersionInfoAsync(mcVersion); + var client = new MinecraftClient() { - foreach (var item in library.GetDownloadItem()) - { - downloader.AddItem(item); - } - } - downloader.Start(); - + _version = new Version(mcVersion) + }; + return client; } - + public override async Task ExecuteInstallerAsync(string path) + { + + } } \ No newline at end of file diff --git a/Minecraft/Instance/IClient.cs b/Minecraft/Instance/IClient.cs index 56b5556a..1b4aad64 100644 --- a/Minecraft/Instance/IClient.cs +++ b/Minecraft/Instance/IClient.cs @@ -2,6 +2,7 @@ using PCL.Core.Net; using System.Text.Json.Nodes; using System.Collections.Generic; +using PCL.Core.IO; namespace PCL.Core.Minecraft.Instance; @@ -9,7 +10,9 @@ public interface IClient { static abstract Task GetVersionInfoAsync(string version); static abstract Task UpdateVersionIndexAsync(); - static abstract List AnalysisLibrary(JsonNode versionJson); - static abstract Task GetJsonAsync(string version,string exceptHash); - static abstract Task StartClientInstallAsync(string version,string path); + abstract Task> AnalyzeLibraryAsync(); + abstract Task GetJsonAsync(); + static abstract Task ParseAsync(string version); + abstract Task ExecuteInstallerAsync(string path); + abstract Task> AnalyzeMissingLibraryAsync(); } diff --git a/Minecraft/Instance/InstanceInstallHandler.cs b/Minecraft/Instance/InstanceInstallHandler.cs new file mode 100644 index 00000000..73e436c0 --- /dev/null +++ b/Minecraft/Instance/InstanceInstallHandler.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using PCL.Core.Minecraft.Instance.Clients; + +namespace PCL.Core.Minecraft.Instance; + +public static class InstanceInstallHandler +{ + public static async Task StartClientInstallAsync(IEnumerable clients,string path) + { + var task = new List(); + foreach (var client in clients){ + task.Add(client.GetJsonAsync()); + } + } + +} \ No newline at end of file diff --git a/Minecraft/JavaHelper.cs b/Minecraft/JavaHelper.cs new file mode 100644 index 00000000..39965122 --- /dev/null +++ b/Minecraft/JavaHelper.cs @@ -0,0 +1,43 @@ +using System.Net.Http; +using System.Runtime.InteropServices; +using System.Text.Json.Nodes; +using System.Threading.Tasks; +using PCL.Core.Net; + +namespace PCL.Core.Minecraft; + +public static class JavaHelper +{ + private static string[] _GetJavaIndexUrl() => [ + "https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json", + "https://bmclapi2.bangbang93.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json" + ]; + private static JsonNode? _JavaIndex; + public static async Task UpdateJavaIndex() + { + foreach (var url in _GetJavaIndexUrl()) + { + var result = await HttpRequestBuilder.Create(url, HttpMethod.Get).SendAsync(false); + if (!result.IsSuccess) continue; + _JavaIndex = await result.AsJsonAsync(); + } + throw new HttpRequestException("Failed to download version json:All of source unavailable"); + } + public static string GetIndexUrlByVersion(int mojarVersaion) { + foreach (var kvp in _JavaIndex!.AsObject()) + { + if (kvp.Key == "gamecore") continue; + var os = kvp.Key; + if (os.Contains("-")) os = os.Split("-")[0]; + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Create(os.ToUpper()))) continue; + foreach (var javas in kvp.Value!.AsObject()) + { + + } + } + return string.Empty; + } + public static string GetIndexUrlByName(string name) { + return string.Empty; + } +} \ No newline at end of file