diff --git a/MinecraftLaunch.Base/Interfaces/IVerifiableDependency.cs b/MinecraftLaunch.Base/Interfaces/IVerifiableDependency.cs
index d24556f..2588d8c 100644
--- a/MinecraftLaunch.Base/Interfaces/IVerifiableDependency.cs
+++ b/MinecraftLaunch.Base/Interfaces/IVerifiableDependency.cs
@@ -1,6 +1,8 @@
-namespace MinecraftLaunch.Base.Interfaces;
+using MinecraftLaunch.Base.Models.SHA1;
+
+namespace MinecraftLaunch.Base.Interfaces;
public interface IVerifiableDependency {
long? Size { get; }
- string Sha1 { get; }
+ Sha1Data? Sha1 { get; }
}
diff --git a/MinecraftLaunch.Base/MinecraftLaunch.Base.csproj b/MinecraftLaunch.Base/MinecraftLaunch.Base.csproj
index 3105013..b98a695 100644
--- a/MinecraftLaunch.Base/MinecraftLaunch.Base.csproj
+++ b/MinecraftLaunch.Base/MinecraftLaunch.Base.csproj
@@ -18,12 +18,18 @@
disable
logo.png
False
+ true
-
+
+
+
+
+
+
diff --git a/MinecraftLaunch.Base/Models/Game/MinecraftEntry.cs b/MinecraftLaunch.Base/Models/Game/MinecraftEntry.cs
index c45a677..7620ab1 100644
--- a/MinecraftLaunch.Base/Models/Game/MinecraftEntry.cs
+++ b/MinecraftLaunch.Base/Models/Game/MinecraftEntry.cs
@@ -1,10 +1,15 @@
+using System.Buffers;
+using System.Diagnostics;
using MinecraftLaunch.Base.Interfaces;
using MinecraftLaunch.Base.Utilities;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
-using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
+using MinecraftLaunch.Base.Models.SHA1;
+#if DEBUG
+using Xunit;
+#endif
namespace MinecraftLaunch.Base.Models.Game;
@@ -73,34 +78,30 @@ private static bool IsLibraryEnabled(IEnumerable rules) {
_ => false,
};
}
-
+
public IEnumerable GetRequiredAssets() {
// Identify file paths
- string assetIndexJsonPath = AssetIndexJsonPath;
- if (this is ModifiedMinecraftEntry { HasInheritance: true } instance)
- assetIndexJsonPath = instance.InheritedMinecraft.AssetIndexJsonPath;
-
+ var assetIndexJsonPath =
+ this is ModifiedMinecraftEntry { HasInheritance: true } inner
+ ? inner.InheritedMinecraft.AssetIndexJsonPath
+ : this.AssetIndexJsonPath;
// Parse asset index json
- Dictionary assets;
- // 这里不用using表达式语法是为了在yield前释放资源,防止被挂起导致池化内存压力增大
- using (var stream = File.OpenRead(assetIndexJsonPath))
- using (var doc = JsonDocument.Parse(stream))
- {
- var root = doc.RootElement;
- if (!root.TryGetProperty("objects"u8, out var value))
- throw new InvalidDataException("Error in parsing asset index json file");
- assets = value.Deserialize(AssetJsonEntryContext.Default.DictionaryStringAssetJsonEntry)
- ?? throw new InvalidDataException("Error in parsing asset index json file");
- }
+
+ using var stream = File.OpenRead(assetIndexJsonPath);
+ using var doc = JsonDocument.Parse(stream);
+ var root = doc.RootElement;
+ if (!root.TryGetProperty("objects"u8, out var value))
+ throw new InvalidDataException("Error in parsing asset index json file");
+ var assets = value;
// Parse GameAsset objects
- foreach (var (key, assetJsonNode) in assets) {
- int size = assetJsonNode.Size;
- string hash = assetJsonNode.Hash ?? throw new InvalidDataException("Invalid asset index");
+ foreach (var item in assets.EnumerateObject()) {
+ var size = item.Value.GetProperty("size"u8).GetInt32();
+ var hash = item.Value.GetProperty("hash"u8).Deserialize(Sha1Data.Sha1DataSerializerContext.Default.Sha1Data);
yield return new MinecraftAsset {
MinecraftFolderPath = MinecraftFolderPath,
- Key = key,
+ Key = item.Name,
Sha1 = hash,
Size = size
};
@@ -110,20 +111,26 @@ public IEnumerable GetRequiredAssets() {
public (IEnumerable Libraries, IEnumerable NativeLibraries) GetRequiredLibraries() {
List libs = [];
List nativeLibs = [];
-
- using var stream = File.OpenRead(ClientJsonPath);
- using var doc = JsonDocument.Parse(stream);
- var root = doc.RootElement;
- if(!root.TryGetProperty("libraries"u8,out var librariesElement))throw new InvalidDataException("client.json does not contain library information");
- var libNodes = librariesElement.Deserialize(LibraryEntriesContext.Default.IEnumerableLibraryEntry)
+ IEnumerable libNodes;
+ using (var stream = File.OpenRead(ClientJsonPath))
+ using (var doc = JsonDocument.Parse(stream))
+ {
+ var root = doc.RootElement;
+ if (!root.TryGetProperty("libraries"u8, out var librariesElement))
+ throw new InvalidDataException("client.json does not contain library information");
+ libNodes =
+ librariesElement.Deserialize(LibraryEntriesContext.Default.IEnumerableLibraryEntry)
?? throw new InvalidDataException("client.json does not contain library information");
+ }
- foreach (var libNode in libNodes) {
+ foreach (var libNode in libNodes)
+ {
if (libNode is null)
continue;
// Check if a library is enabled
- if (libNode.Rules is { } libRules) {
+ if (libNode.Rules is { } libRules)
+ {
if (!IsLibraryEnabled(libRules))
continue;
}
@@ -133,7 +140,8 @@ public IEnumerable GetRequiredAssets() {
if (gameLib.IsNativeLibrary)
nativeLibs.Add(gameLib);
- else {
+ else
+ {
libs.Add(gameLib);
}
}
@@ -219,7 +227,9 @@ public MinecraftLibrary(string mavenName) {
internal string GetLibraryPath() => GetLibraryPath(this.MavenName);
- internal static string GetLibraryPath(string mavenName) {
+#if DEBUG
+
+ internal static string GetLibraryPathOld(string mavenName) {
string path = "";
var extension = mavenName.Contains('@') ? mavenName.Split('@') : [];
@@ -240,6 +250,124 @@ internal static string GetLibraryPath(string mavenName) {
return Path.Combine(path, filename);
}
+
+ [Theory]
+ [Trait("Category", "Debug")]
+ // 这些是AI生成的测试样例
+ // 基础格式 (group:artifact:version)
+ [InlineData("com.google.guava:guava:31.1-jre")]
+ [InlineData("org.springframework:spring-core:5.3.23")]
+ [InlineData("io.netty:netty-all:4.1.82.Final")]
+// 带 Classifier (group:artifact:version:classifier)
+ [InlineData("org.lwjgl:lwjgl:3.3.1:natives-windows")]
+ [InlineData("com.android.tools.build:gradle:7.2.0:sources")]
+ [InlineData("org.jetbrains.kotlin:kotlin-stdlib:1.7.10:javadoc")]
+// 带扩展名 (group:artifact:version@extension)
+ [InlineData("com.google.android:android:4.1.1.4@aar")]
+ [InlineData("androidx.appcompat:appcompat:1.5.1@aar")]
+ [InlineData("com.squareup.okhttp3:okhttp:4.10.0@pom")]
+// 完整格式 (group:artifact:version:classifier@extension)
+ [InlineData("org.lwjgl:lwjgl-glfw:3.3.1:natives-linux@jar")]
+ [InlineData("com.android.support:support-v4:28.0.0:sources@jar")]
+ [InlineData("io.fabric8:kubernetes-client:6.0.0:tests@jar")]
+// 多层级 Group
+ [InlineData("org.apache.logging.log4j:log4j-core:2.19.0")]
+ [InlineData("com.fasterxml.jackson.core:jackson-databind:2.13.4")]
+ [InlineData("org.hibernate.validator:hibernate-validator:7.0.5.Final")]
+// 边界情况
+ [InlineData("a:b:1.0")]
+ [InlineData("com.example:my-lib:1.0.0-SNAPSHOT")]
+ [InlineData("org.test:artifact:1.0-beta.1:classifier@zip")]
+ [InlineData("io.a.b.c.d.e.f:deep-artifact:1.0.0")]
+// 特殊版本号
+ [InlineData("com.google.code.gson:gson:2.10.1")]
+ [InlineData("org.junit.jupiter:junit-jupiter:5.9.0-M1")]
+ [InlineData("com.squareup.retrofit2:retrofit:2.9.0-RC1")]
+ public static void T2T(string src)
+ {
+ var libraryPathOld = GetLibraryPathOld(src);
+ var actualMemory = GetLibraryPath(src);
+ Console.WriteLine(actualMemory);
+ Console.WriteLine(libraryPathOld);
+ Assert.Equal(libraryPathOld,actualMemory);
+ }
+#endif
+
+ internal static string GetLibraryPath(string mavenName)
+ {
+ scoped Span extensionRanges = stackalloc Range[2];
+ var extensionCount = mavenName.AsSpan().Split(extensionRanges, '@');
+ var mainSpan = mavenName.AsSpan(extensionRanges[0]);
+ var extensionSpan = extensionCount > 1 ? mavenName.AsSpan(extensionRanges[1]) : default;
+
+ scoped Span subRanges = stackalloc Range[4];
+ var subCount = mainSpan.Split(subRanges, ':');
+ Debug.Assert(subCount >= 3, "Maven name must have at least group:artifact:version");
+
+ // 申请缓冲区
+ var bufferSize = mavenName.Length + mainSpan[subRanges[0]].Length + 40;
+ var buffer = ArrayPool.Shared.Rent(bufferSize);
+ var offset = 0;
+
+ try
+ {
+ // 1. 处理 Group name(替换 . 为 Path.DirectorySeparatorChar)
+ scoped var groupSpan = mainSpan[subRanges[0]];
+ groupSpan.Replace(buffer, '.', Path.DirectorySeparatorChar);
+ offset += groupSpan.Length;
+ buffer[offset++] = Path.DirectorySeparatorChar;
+
+ // 2. 处理 Artifact name
+ scoped var artifactSpan = mainSpan[subRanges[1]];
+ artifactSpan.CopyTo(buffer.AsSpan(offset));
+ offset += artifactSpan.Length;
+ buffer[offset++] = Path.DirectorySeparatorChar;
+
+ // 3. 处理 Version
+ scoped var versionSpan = mainSpan[subRanges[2]];
+ versionSpan.CopyTo(buffer.AsSpan(offset));
+ offset += versionSpan.Length;
+ buffer[offset++] = Path.DirectorySeparatorChar;
+
+ // 4.1 Artifact
+ artifactSpan.CopyTo(buffer.AsSpan(offset));
+ offset += artifactSpan.Length;
+
+ // 4.2 -version
+ buffer[offset++] = '-';
+ versionSpan.CopyTo(buffer.AsSpan(offset));
+ offset += versionSpan.Length;
+
+ // 4.3 -classifier
+ if (subCount > 3)
+ {
+ buffer[offset++] = '-';
+ scoped var classifierSpan = mainSpan[subRanges[3]];
+ classifierSpan.CopyTo(buffer.AsSpan(offset));
+ offset += classifierSpan.Length;
+ }
+
+ // 4.4 .extension
+ buffer[offset++] = '.';
+ if (!extensionSpan.IsEmpty)
+ {
+ extensionSpan.CopyTo(buffer.AsSpan(offset));
+ offset += extensionSpan.Length;
+ }
+ else
+ {
+ "jar".AsSpan().CopyTo(buffer.AsSpan(offset));
+ offset += 3;
+ }
+
+ // 5. 生成最终字符串
+ return new string(buffer.AsSpan(0, offset));
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(buffer);
+ }
+ }
public static MinecraftLibrary ParseJsonNode(LibraryEntry libNode, string minecraftFolderPath) {
// Check platform-specific library name
@@ -251,7 +379,7 @@ public static MinecraftLibrary ParseJsonNode(LibraryEntry libNode, string minecr
if (libNode.DownloadInformation != null) {
DownloadArtifactEntry artifactNode = GetLibraryArtifactInfo(libNode);
- if (artifactNode.Sha1 is null || artifactNode.Size is null || artifactNode.Url is null)
+ if (artifactNode.Size is null || artifactNode.Url is null)
throw new InvalidDataException("Invalid artifact node");
#region Vanilla Pattern
@@ -314,7 +442,7 @@ public static MinecraftLibrary ParseJsonNode(LibraryEntry libNode, string minecr
|| libNode.ServerRequest != null) {
string legacyForgeLibraryUrl = (libNode.MavenUrl == "https://maven.minecraftforge.net/"
? "https://maven.minecraftforge.net/"
- : "https://libraries.minecraft.net/") + GetLibraryPath(libNode.MavenName).Replace("\\", "/");
+ : "https://libraries.minecraft.net/") + GetLibraryPath(libNode.MavenName).Replace('\\','/');
return new LegacyForgeLibrary(libNode.MavenName, legacyForgeLibraryUrl) {
MinecraftFolderPath = minecraftFolderPath,
@@ -331,8 +459,8 @@ public static MinecraftLibrary ParseJsonNode(LibraryEntry libNode, string minecr
return new FabricLibrary(libNode.MavenName) {
MinecraftFolderPath = minecraftFolderPath,
IsNativeLibrary = false,
- Size = libNode?.Size,
- Sha1 = libNode?.Sha1
+ Size = libNode.Size,
+ Sha1 = libNode.Sha1!.Value
};
}
@@ -351,9 +479,9 @@ public static MinecraftLibrary ParseJsonNode(LibraryEntry libNode, string minecr
#endregion
#region OptiFine Pattern
-
- if (libNode.MavenName.StartsWith("optifine:optifine", StringComparison.CurrentCultureIgnoreCase)
- || libNode.MavenName.StartsWith("optifine:launchwrapper-of", StringComparison.CurrentCultureIgnoreCase)) {
+
+ if (libNode.MavenName.StartsWith("optifine:optifine", StringComparison.Ordinal)
+ || libNode.MavenName.StartsWith("optifine:launchwrapper-of", StringComparison.Ordinal)) {
return new OptiFineLibrary(libNode.MavenName) {
IsNativeLibrary = false,
MinecraftFolderPath = minecraftFolderPath
@@ -395,35 +523,54 @@ public override bool Equals(object obj) {
private static partial Regex GenerateMavenParseRegex();
}
-public class MinecraftClient : MinecraftDependency, IDownloadDependency, IVerifiableDependency {
+public sealed class MinecraftClient : MinecraftDependency, IDownloadDependency, IVerifiableDependency {
public override string FilePath => Path.Combine("versions", ClientId, $"{ClientId}.jar");
public required string ClientId { get; init; }
public required string Url { get; init; }
public required long? Size { get; init; }
long? IVerifiableDependency.Size => Size;
- public required string Sha1 { get; init; }
+ public required Sha1Data? Sha1 { get; init; }
}
public sealed class MinecraftAsset : MinecraftDependency, IDownloadDependency, IVerifiableDependency {
public required string Key { get; set; }
public required long? Size { get; init; }
- public required string Sha1 { get; init; }
- public string Url => $"https://resources.download.minecraft.net/{Sha1[0..2]}/{Sha1}";
- public override string FilePath => Path.Combine("assets", "objects", Sha1[0..2], Sha1);
+ public required Sha1Data? Sha1 { get; init; }
+
+
+ public string Url
+ {
+ get
+ {
+ var buf = (Span)stackalloc char[40];
+ Sha1!.Value.FormatTo(buf);
+ return $"https://resources.download.minecraft.net/{buf[..2]}/{buf}";
+ }
+ }
+
+ public override string FilePath
+ {
+ get
+ {
+ var buf = (Span)stackalloc char[40];
+ Sha1!.Value.FormatTo(buf);
+ return $"assets{Path.DirectorySeparatorChar}objects{Path.DirectorySeparatorChar}{buf[..2]}{Path.DirectorySeparatorChar}{buf}";
+ }
+ }
long? IVerifiableDependency.Size => Size;
}
-public record DownloadArtifactEntry {
+public sealed record DownloadArtifactEntry {
[JsonPropertyName("url")] public string Url { get; set; }
[JsonPropertyName("size")] public long? Size { get; set; }
[JsonPropertyName("path")] public string Path { get; set; }
- [JsonPropertyName("sha1")] public string Sha1 { get; set; }
+ [JsonPropertyName("sha1")] public Sha1Data? Sha1 { get; set; }
}
-public record LibraryEntry {
+public sealed record LibraryEntry {
[JsonPropertyName("size")] public long? Size { get; set; }
- [JsonPropertyName("sha1")] public string Sha1 { get; set; }
+ [JsonPropertyName("sha1")] public Sha1Data? Sha1 { get; set; }
[JsonPropertyName("url")] public string MavenUrl { get; set; }
[JsonPropertyName("clientreq")] public bool? ClientRequest { get; set; }
[JsonPropertyName("serverreq")] public bool? ServerRequest { get; set; }
@@ -436,17 +583,17 @@ public record LibraryEntry {
public string MavenName { get; set; }
}
-public record DownloadInformationEntry {
+public sealed record DownloadInformationEntry {
[JsonPropertyName("artifact")] public DownloadArtifactEntry Artifact { get; set; }
[JsonPropertyName("classifiers")] public Dictionary Classifiers { get; set; }
}
-public record RuleEntry {
+public sealed record RuleEntry {
[JsonPropertyName("os")] public Os System { get; set; }
[JsonPropertyName("action")] public string Action { get; set; }
}
-public record Os {
+public sealed record Os {
[JsonPropertyName("name")] public string Name { get; set; }
[JsonPropertyName("arch")] public string Arch { get; set; }
[JsonPropertyName("version")] public string Version { get; set; }
@@ -457,15 +604,17 @@ public class ForgeLibrary(string mavenName) : MinecraftLibrary(mavenName), IDown
public required long? Size { get; init; }
public required string Url { get; init; }
- public required string Sha1 { get; init; }
+ public required Sha1Data? Sha1 { get; init; }
}
public sealed class VanillaLibrary(string mavenName) : MinecraftLibrary(mavenName), IDownloadDependency, IVerifiableDependency {
+ private string _url;
long? IVerifiableDependency.Size => Size;
public required long? Size { get; init; }
- public required string Sha1 { get; init; }
- public string Url => $"https://libraries.minecraft.net/{GetLibraryPath().Replace("\\", "/")}";
+ public required Sha1Data? Sha1 { get; init; }
+ // 值是不变的,添加缓存
+ public string Url => _url ??= $"https://libraries.minecraft.net/{GetLibraryPath().Replace('\\', '/')}";
}
public sealed class NeoForgeLibrary(string mavenName) : ForgeLibrary(mavenName);
@@ -480,22 +629,25 @@ public sealed class LegacyForgeLibrary(string mavenName, string url) : Minecraft
public sealed class OptiFineLibrary(string mavenName) : MinecraftLibrary(mavenName);
public sealed class FabricLibrary(string mavenName) : MinecraftLibrary(mavenName), IDownloadDependency, IVerifiableDependency {
+ private string _url;
long? IVerifiableDependency.Size => Size;
public long? Size { get; set; }
- public string Sha1 { get; set; }
- public string Url => $"https://maven.fabricmc.net/{GetLibraryPath().Replace("\\", "/")}";
+ public Sha1Data? Sha1 { get; set; }
+ // 添加缓存
+ public string Url => _url ??=$"https://maven.fabricmc.net/{GetLibraryPath().Replace('\\', '/')}";
}
-public class QuiltLibrary(string mavenName) : MinecraftLibrary(mavenName), IDownloadDependency, IVerifiableDependency {
+public sealed class QuiltLibrary(string mavenName) : MinecraftLibrary(mavenName), IDownloadDependency, IVerifiableDependency {
+ private string _url;
long? IVerifiableDependency.Size => Size;
public long? Size { get; set; }
- public string Sha1 { get; set; }
- public string Url => $"https://maven.quiltmc.org/repository/release/{GetLibraryPath().Replace("\\", "/")}";
+ public Sha1Data? Sha1 { get; set; }
+ public string Url => _url ??= $"https://maven.quiltmc.org/repository/release/{GetLibraryPath().Replace('\\', '/')}";
}
-public class DownloadableDependency(string mavenName, string url) : MinecraftLibrary(mavenName), IDownloadDependency {
+public sealed class DownloadableDependency(string mavenName, string url) : MinecraftLibrary(mavenName), IDownloadDependency {
long? IDownloadDependency.Size => throw new NotSupportedException();
public string Url { get; init; } = url;
@@ -503,9 +655,9 @@ public class DownloadableDependency(string mavenName, string url) : MinecraftLib
public sealed class UnknownLibrary(string mavenName) : MinecraftLibrary(mavenName);
-public record AssetJsonEntry {
+public sealed record AssetJsonEntry {
[JsonPropertyName("size")] public int Size { get; set; }
- [JsonPropertyName("hash")] public string Hash { get; set; }
+ [JsonPropertyName("hash")] public Sha1Data Hash { get; set; }
}
[JsonSerializable(typeof(IEnumerable))]
diff --git a/MinecraftLaunch.Base/Models/Game/MinecraftJsonEntry.cs b/MinecraftLaunch.Base/Models/Game/MinecraftJsonEntry.cs
index 2459194..2277bcf 100644
--- a/MinecraftLaunch.Base/Models/Game/MinecraftJsonEntry.cs
+++ b/MinecraftLaunch.Base/Models/Game/MinecraftJsonEntry.cs
@@ -1,7 +1,7 @@
using System.Text.Json;
using MinecraftLaunch.Base.Interfaces;
-using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
+using MinecraftLaunch.Base.Models.SHA1;
namespace MinecraftLaunch.Base.Models.Game;
@@ -10,7 +10,7 @@ public class AssstIndex : MinecraftDependency, IDownloadDependency, IVerifiableD
[JsonPropertyName("id")] public string Id { get; set; }
[JsonPropertyName("url")] public string Url { get; set; }
- [JsonPropertyName("sha1")] public string Sha1 { get; set; }
+ [JsonPropertyName("sha1")] public Sha1Data? Sha1 { get; set; }
[JsonPropertyName("size")] public long? Size { get; set; }
[JsonIgnore] public override string FilePath => Path.Combine("assets", "indexes", $"{Id}.json");
@@ -35,7 +35,7 @@ public record AssstIndexJsonEntry {
[JsonPropertyName("id")] public string Id { get; set; }
[JsonPropertyName("size")] public int Size { get; set; }
[JsonPropertyName("url")] public string Url { get; set; }
- [JsonPropertyName("sha1")] public string Sha1 { get; set; }
+ [JsonPropertyName("sha1")] public Sha1Data Sha1 { get; set; }
[JsonPropertyName("totalSize")] public int TotalSize { get; set; }
}
diff --git a/MinecraftLaunch.Base/Models/Network/CurseforgeResource.cs b/MinecraftLaunch.Base/Models/Network/CurseforgeResource.cs
index 6224ba2..3f5beef 100644
--- a/MinecraftLaunch.Base/Models/Network/CurseforgeResource.cs
+++ b/MinecraftLaunch.Base/Models/Network/CurseforgeResource.cs
@@ -1,5 +1,6 @@
using MinecraftLaunch.Base.Enums;
using MinecraftLaunch.Base.Interfaces;
+using MinecraftLaunch.Base.Models.SHA1;
namespace MinecraftLaunch.Base.Models.Network;
@@ -32,7 +33,7 @@ public record CurseforgeResourceFile {
public required bool IsAvailable { get; init; }
public required bool IsServerPack { get; init; }
- public required string Sha1 { get; init; }
+ public required Sha1Data? Sha1 { get; init; }
public required string FileName { get; init; }
public required string DisplayName { get; init; }
public required string DownloadUrl { get; init; }
diff --git a/MinecraftLaunch.Base/Models/Network/FileHashes.cs b/MinecraftLaunch.Base/Models/Network/FileHashes.cs
index 7acf092..53b3677 100644
--- a/MinecraftLaunch.Base/Models/Network/FileHashes.cs
+++ b/MinecraftLaunch.Base/Models/Network/FileHashes.cs
@@ -1,8 +1,10 @@
+using MinecraftLaunch.Base.Models.SHA1;
+
namespace MinecraftLaunch.Base.Models.Network;
public record FileHashes
{
public required string Sha512 { get; init; }
- public required string Sha1 { get; init; }
+ public required Sha1Data Sha1 { get; init; }
}
diff --git a/MinecraftLaunch.Base/Models/Network/McbbsModpackInstallEntry.cs b/MinecraftLaunch.Base/Models/Network/McbbsModpackInstallEntry.cs
index d9be4eb..d815ce3 100644
--- a/MinecraftLaunch.Base/Models/Network/McbbsModpackInstallEntry.cs
+++ b/MinecraftLaunch.Base/Models/Network/McbbsModpackInstallEntry.cs
@@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
+using MinecraftLaunch.Base.Models.SHA1;
namespace MinecraftLaunch.Base.Models.Network;
@@ -20,7 +21,7 @@ public record McbbsModpackAddons {
public record McbbsModpackFileEntry {
[JsonPropertyName("path")] public string Path { get; set; }
- [JsonPropertyName("hash")] public string Hash { get; set; }
+ [JsonPropertyName("hash")] public Sha1Data? Hash { get; set; }
[JsonPropertyName("type")] public string Type { get; set; }
[JsonPropertyName("force")] public bool IsForce { get; set; }
}
diff --git a/MinecraftLaunch.Base/Models/Network/ModrinthResource.cs b/MinecraftLaunch.Base/Models/Network/ModrinthResource.cs
index 535827b..30dbe73 100644
--- a/MinecraftLaunch.Base/Models/Network/ModrinthResource.cs
+++ b/MinecraftLaunch.Base/Models/Network/ModrinthResource.cs
@@ -1,6 +1,6 @@
using MinecraftLaunch.Base.Enums;
using MinecraftLaunch.Base.Interfaces;
-using System;
+using MinecraftLaunch.Base.Models.SHA1;
namespace MinecraftLaunch.Base.Models.Network;
@@ -31,7 +31,7 @@ public record ModrinthResourceFile {
public FileReleaseType ReleaseType { get; init; }
- public required string Sha1 { get; init; }
+ public required Sha1Data Sha1 { get; init; }
public required string Sha512 { get; init; }
public required string FileName { get; init; }
public required string DownloadUrl { get; init; }
diff --git a/MinecraftLaunch.Base/Models/SHA1/Sha1DataConvert.cs b/MinecraftLaunch.Base/Models/SHA1/Sha1DataConvert.cs
new file mode 100644
index 0000000..007b6a6
--- /dev/null
+++ b/MinecraftLaunch.Base/Models/SHA1/Sha1DataConvert.cs
@@ -0,0 +1,121 @@
+using System.Runtime.CompilerServices;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+
+namespace MinecraftLaunch.Base.Models.SHA1;
+
+
+[JsonConverter(typeof(Sha1DataJsonConverter))]
+[InlineArray(20)]
+public partial struct Sha1Data : IEquatable
+{
+ public bool Equals(Sha1Data other) => _data == other._data;
+
+ public override bool Equals(object obj) => obj is Sha1Data other && Equals(other);
+
+ public override int GetHashCode() => _data.GetHashCode();
+
+ private byte _data;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void FormatTo(Span destination)
+ {
+ for (var i = 0; i < 20; i++)
+ {
+ var b = this[i];
+ destination[i * 2] = ToHexCharLower(b >> 4);
+ destination[i * 2 + 1] = ToHexCharLower(b & 0x0F);
+ }
+ return;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ static char ToHexCharLower(int b) => b switch
+ {
+ < 10 => (char)('0' + b),
+ _ => (char)('a' + (b - 10))
+ };
+ }
+ ///
+ /// 返回 40 位小写十六进制字符串。
+ ///
+ public override string ToString()
+ {
+ // 栈上分配 40 个 char,避免托管内存分配
+ Span buffer = stackalloc char[40];
+ FormatTo(buffer);
+ return new string(buffer);
+ }
+
+
+
+ public sealed class Sha1DataJsonConverter : JsonConverter
+{
+ public override Sha1Data Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (typeToConvert != typeof(Sha1Data))
+ {
+ ThrowHelper.ThrowInvalidTargetType();
+ }
+ if (reader.TokenType != JsonTokenType.String)
+ ThrowHelper.ThrowUnexpectedTokenType();
+
+ var utf8Hex = reader.ValueSpan;
+ if (utf8Hex.Length != 40)
+ ThrowHelper.ThrowInvalidLength(utf8Hex.Length);
+ Unsafe.SkipInit(out Sha1Data result);
+ for (var i = 0; i < 20; i++)
+ {
+ var idx = i * 2;
+ result[i] = ParseHex(utf8Hex[idx], utf8Hex[idx + 1]);
+ }
+ return result;
+ }
+
+
+ public override void Write(Utf8JsonWriter writer, Sha1Data value, JsonSerializerOptions options)
+ {
+ var buffer = (Span)stackalloc byte[40];
+ for (var i = 0; i < 20; i++)
+ {
+ var b = value[i];
+ buffer[i * 2] = ToHexCharLower(b >> 4);
+ buffer[i * 2 + 1] = ToHexCharLower(b & 0x0F);
+ }
+ writer.WriteStringValue(buffer);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static byte ParseHex(byte c1, byte c2)
+ {
+ var h = (c1 & 0x0F) + ((c1 & 0x40) != 0 ? 9 : 0);
+ var l = (c2 & 0x0F) + ((c2 & 0x40) != 0 ? 9 : 0);
+ return (byte)((h << 4) | l);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static byte ToHexCharLower(int b) => b switch
+ {
+ < 10 => (byte)('0' + b),
+ _ => (byte)('a' + (b - 10))
+ };
+
+ private static class ThrowHelper
+ {
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static void ThrowUnexpectedTokenType() => throw new JsonException("Unexpected token type for SHA1 data, expected a string.");
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static void ThrowInvalidLength(int len) => throw new JsonException($"SHA1 hex string must be 40 characters long, got {len}.");
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static void ThrowInvalidTargetType()
+ {
+ throw new JsonException("Invalid target type.");
+ }
+ }
+}
+
+ [JsonSerializable(typeof(Sha1Data))]
+ [JsonSerializable(typeof(Sha1Data[]))]
+ public sealed partial class Sha1DataSerializerContext : JsonSerializerContext;
+}
+
diff --git a/MinecraftLaunch/Components/Authenticator/MicrosoftAuthenticator.cs b/MinecraftLaunch/Components/Authenticator/MicrosoftAuthenticator.cs
index b7da666..f23cb43 100644
--- a/MinecraftLaunch/Components/Authenticator/MicrosoftAuthenticator.cs
+++ b/MinecraftLaunch/Components/Authenticator/MicrosoftAuthenticator.cs
@@ -31,9 +31,8 @@ public async Task RefreshAsync(MicrosoftAccount account, Cance
var result = await request.PostAsync(new FormUrlEncodedContent(payload),
cancellationToken: cancellationToken);
- var json = await result.GetStringAsync();
- var response = json.Deserialize(OAuth2TokenResponseContext.Default.OAuth2TokenResponse);
-
+ await using var json = await result.GetStreamAsync();
+ var response = await JsonSerializer.DeserializeAsync(json,OAuth2TokenResponseContext.Default.OAuth2TokenResponse, cancellationToken);
return await AuthenticateAsync(response, cancellationToken);
}
@@ -73,10 +72,10 @@ public async Task DeviceFlowAuthAsync(Action RefreshAsync(YggdrasilAccount account, Cance
YggdrasilRequestPayloadContext.Default.YggdrasilRefreshPayload),
cancellationToken: cancellationToken);
- var json = await responseMessage.GetStringAsync();
- var entry = json.Deserialize(YggdrasilResponseContext.Default.YggdrasilResponse);
+ await using var json = await responseMessage.GetStreamAsync();
+ var entry = await JsonSerializer.DeserializeAsync(json,YggdrasilResponseContext.Default.YggdrasilResponse, cancellationToken);
var profile = entry.SelectedProfile;
return new YggdrasilAccount(profile.Name, Guid.Parse(profile.Id), entry.AccessToken, _url, entry.ClientToken);
@@ -65,8 +66,8 @@ public async Task> AuthenticateAsync(CancellationT
YggdrasilRequestPayloadContext.Default.YggdrasilAuthenticatePayload),
cancellationToken: cancellationToken);
- var json = await responseMessage.GetStringAsync();
- var entry = json.Deserialize(YggdrasilResponseContext.Default.YggdrasilResponse);
+ await using var json = await responseMessage.GetStreamAsync();
+ var entry = await JsonSerializer.DeserializeAsync(json,YggdrasilResponseContext.Default.YggdrasilResponse, cancellationToken);
return entry.AvailableProfiles.Select(profile =>
new YggdrasilAccount(profile.Name, Guid.Parse(profile.Id), entry.AccessToken, _url, entry.ClientToken));
diff --git a/MinecraftLaunch/Components/Downloader/MinecraftResourceDownloader.cs b/MinecraftLaunch/Components/Downloader/MinecraftResourceDownloader.cs
index 6d500d2..a1dc41e 100644
--- a/MinecraftLaunch/Components/Downloader/MinecraftResourceDownloader.cs
+++ b/MinecraftLaunch/Components/Downloader/MinecraftResourceDownloader.cs
@@ -111,15 +111,10 @@ private static bool VerifyDependency(MinecraftDependency dep, CancellationToken
bool VerifySha1() {
using var fileStream = File.OpenRead(dep.FullPath);
- byte[] sha1Bytes = SHA1.HashData(fileStream);
-
-#if NET9_0_OR_GREATER
- string sha1Str = Convert.ToHexStringLower(sha1Bytes);
-#else
- string sha1Str = BitConverter.ToString(sha1Bytes).Replace("-", string.Empty).ToLowerInvariant();
-#endif
-
- return sha1Str == verifiableDependency.Sha1;
+ var sha1Bytes = (Span)stackalloc byte[20];
+ SHA1.HashData(fileStream, sha1Bytes);
+ var sp = verifiableDependency.Sha1.Value;
+ return sha1Bytes.SequenceEqual(sp);
}
bool VerifySize() {
diff --git a/MinecraftLaunch/Components/Installer/FabricInstaller.cs b/MinecraftLaunch/Components/Installer/FabricInstaller.cs
index e748781..8ae4fc3 100644
--- a/MinecraftLaunch/Components/Installer/FabricInstaller.cs
+++ b/MinecraftLaunch/Components/Installer/FabricInstaller.cs
@@ -25,10 +25,11 @@ public static FabricInstaller Create(string mcFolder, FabricInstallEntry install
}
public static async Task> EnumerableFabricAsync(string mcVersion, CancellationToken cancellationToken = default) {
- string json = await HttpUtil.FlurlClient.Request($"https://meta.fabricmc.net/v2/versions/loader/{mcVersion}")
- .GetStringAsync(cancellationToken: cancellationToken);
+ await using var json = await HttpUtil.FlurlClient.Request($"https://meta.fabricmc.net/v2/versions/loader/{mcVersion}")
+ .GetStreamAsync(cancellationToken: cancellationToken);
- var entries = json.Deserialize(FabricInstallEntryContext.Default.IEnumerableFabricInstallEntry)
+ var entries = (await JsonSerializer.DeserializeAsync(json,
+ FabricInstallEntryContext.Default.IEnumerableFabricInstallEntry, cancellationToken))
.OrderByDescending(x => new Version(x.Loader.Version.Replace(x.Loader.Separator, ".")));
return entries;
diff --git a/MinecraftLaunch/Components/Installer/ForgeInstaller.cs b/MinecraftLaunch/Components/Installer/ForgeInstaller.cs
index 620fbf0..df1181a 100644
--- a/MinecraftLaunch/Components/Installer/ForgeInstaller.cs
+++ b/MinecraftLaunch/Components/Installer/ForgeInstaller.cs
@@ -67,8 +67,8 @@ await WriteVersionJsonAndSomeDependenciesAsync(isLegacy, doc.RootElement, packag
{
ReportProgress(InstallStep.Interrupted, 1.0d, TaskStatus.Canceled, 1, 1);
ReportCompleted(false, ex);
+ throw;
}
-
return entry ?? throw new ArgumentNullException(nameof(entry), "Unexpected null reference to variable");
}
@@ -77,8 +77,9 @@ public static async Task> EnumerableForgeAsync(st
? $"https://bmclapi2.bangbang93.com/neoforge/list/{mcVersion}"
: $"https://bmclapi2.bangbang93.com/forge/minecraft/{mcVersion}";
- string json = await packagesUrl.GetStringAsync(cancellationToken: cancellationToken);
- var entries = json.Deserialize(ForgeInstallEntryContext.Default.IEnumerableForgeInstallEntry)
+ await using var json = await packagesUrl.GetStreamAsync(cancellationToken: cancellationToken);
+ var entries = (await JsonSerializer.DeserializeAsync(json,
+ ForgeInstallEntryContext.Default.IEnumerableForgeInstallEntry, cancellationToken))
.OrderByDescending(entry => entry.Build);
foreach (var entry in entries)
@@ -130,9 +131,9 @@ private async Task DownloadForgePackageAsync(CancellationToken cancell
var packageFile = new FileInfo(Path.Combine(MinecraftFolder, fileName));
var downloadRequest = new DownloadRequest(packageUrl, packageFile.FullName);
- await new DefaultDownloader()
+ var downloadResult = await new DefaultDownloader()
.DownloadAsync(downloadRequest, cancellationToken);
-
+ if (downloadResult.Type is DownloadResultType.Failed) throw downloadResult.Exception;
ReportProgress(InstallStep.DownloadPackage, 0.45d, TaskStatus.Running, 1, 1);
return packageFile;
diff --git a/MinecraftLaunch/Components/Installer/OptifineInstaller.cs b/MinecraftLaunch/Components/Installer/OptifineInstaller.cs
index ece69ff..77c8071 100644
--- a/MinecraftLaunch/Components/Installer/OptifineInstaller.cs
+++ b/MinecraftLaunch/Components/Installer/OptifineInstaller.cs
@@ -40,8 +40,9 @@ public static OptifineInstaller Create(string mcFolder, OptifineInstallEntry opt
public static async Task> EnumerableOptifineAsync(string mcVersion, CancellationToken cancellationToken = default) {
string url = $"https://bmclapi2.bangbang93.com/optifine/{mcVersion}";
- string json = await url.GetStringAsync(cancellationToken: cancellationToken);
- var entries = json.Deserialize(OptifineInstallEntryContext.Default.IEnumerableOptifineInstallEntry)
+ await using var json = await url.GetStreamAsync(cancellationToken: cancellationToken);
+ var entries = (await JsonSerializer.DeserializeAsync(json,
+ OptifineInstallEntryContext.Default.IEnumerableOptifineInstallEntry, cancellationToken))
.OrderByDescending(entry => entry.Patch);
return entries;
diff --git a/MinecraftLaunch/Components/Installer/QuiltInstaller.cs b/MinecraftLaunch/Components/Installer/QuiltInstaller.cs
index 79770bb..e482f1b 100644
--- a/MinecraftLaunch/Components/Installer/QuiltInstaller.cs
+++ b/MinecraftLaunch/Components/Installer/QuiltInstaller.cs
@@ -25,10 +25,10 @@ public static QuiltInstaller Create(string mcFolder, QuiltInstallEntry installEn
}
public static async Task> EnumerableQuiltAsync(string mcVersion, CancellationToken cancellationToken = default) {
- string json = await $"https://meta.quiltmc.org/v3/versions/loader/{mcVersion}"
- .GetStringAsync(cancellationToken: cancellationToken);
+ await using var json = await $"https://meta.quiltmc.org/v3/versions/loader/{mcVersion}"
+ .GetStreamAsync(cancellationToken: cancellationToken);
- var entries = json.Deserialize(QuiltInstallEntryContext.Default.IEnumerableQuiltInstallEntry);
+ var entries = await JsonSerializer.DeserializeAsync(json,QuiltInstallEntryContext.Default.IEnumerableQuiltInstallEntry, cancellationToken);
return entries;
}
diff --git a/MinecraftLaunch/Components/Logging/LogAnalyzer.cs b/MinecraftLaunch/Components/Logging/LogAnalyzer.cs
index 6fb8b61..a77aa4d 100644
--- a/MinecraftLaunch/Components/Logging/LogAnalyzer.cs
+++ b/MinecraftLaunch/Components/Logging/LogAnalyzer.cs
@@ -3,6 +3,7 @@
using MinecraftLaunch.Base.Models.Logging;
using MinecraftLaunch.Extensions;
using System.Collections.Frozen;
+using System.Diagnostics;
using System.Text.RegularExpressions;
namespace MinecraftLaunch.Components.Logging;
@@ -192,12 +193,13 @@ private IEnumerable TryFindSuspiciousModId(IEnumerable logs, str
details = details.Replace("Fabric Mods", "¨");
details = details.Split('¨').LastOrDefault();
+ Debug.Assert(details is not null);
//The FoegeMod is get all has the ".jar" lines and
//the fabricmod is get all has the "Mod" lines.
var modLines = new List();
foreach (var detail in details.Split(Environment.NewLine))
- if (detail.Contains(".jar", StringComparison.CurrentCultureIgnoreCase) || (isFabricMod && detail.StartsWith("\t" + "\t") && !FabricModIdentifier().IsMatch(detail)))
+ if (detail.Contains(".jar", StringComparison.OrdinalIgnoreCase) || (isFabricMod && detail.StartsWith("\t\t", StringComparison.Ordinal) && !FabricModIdentifier().IsMatch(detail)))
modLines.Add(detail);
var hintLines = new List();
diff --git a/MinecraftLaunch/Components/Parser/MinecraftParser.cs b/MinecraftLaunch/Components/Parser/MinecraftParser.cs
index 4616c83..ad93670 100644
--- a/MinecraftLaunch/Components/Parser/MinecraftParser.cs
+++ b/MinecraftLaunch/Components/Parser/MinecraftParser.cs
@@ -50,18 +50,18 @@ public List GetMinecrafts() {
if (!versionsDirectory.Exists)
return [];
- foreach (DirectoryInfo dir in versionsDirectory.EnumerateDirectories()) {
- try {
- var entry = Parse(dir, list, out bool inheritedInstanceAlreadyFound);
- int index = list.FindIndex(i => i.Id == entry.Id);
- if (index != -1) {
- list.RemoveAt(index);
- }
-
- list.Add(entry);
- if (entry is ModifiedMinecraftEntry m && m.HasInheritance && !inheritedInstanceAlreadyFound)
- list.Add(m.InheritedMinecraft);
- } catch (Exception) { }
+ foreach (DirectoryInfo dir in versionsDirectory.EnumerateDirectories())
+ {
+ var entry = Parse(dir, list, out bool inheritedInstanceAlreadyFound);
+ int index = list.FindIndex(i => i.Id == entry.Id);
+ if (index != -1)
+ {
+ list.RemoveAt(index);
+ }
+
+ list.Add(entry);
+ if (entry is ModifiedMinecraftEntry m && m.HasInheritance && !inheritedInstanceAlreadyFound)
+ list.Add(m.InheritedMinecraft);
}
foreach (var processor in DataProcessors.Values) {
@@ -192,8 +192,7 @@ private static ModifiedMinecraftEntry ParseModified(PartialData partialData, Min
// Find the inherited instance
string inheritedInstanceId = minecraftJsonEntry.InheritsFrom
?? throw new InvalidOperationException("InheritsFrom is not defined in client.json");
-
- inheritedEntry = minecraftEntries.FirstOrDefault(i => i is VanillaMinecraftEntry v && v.Version.VersionId == inheritedInstanceId) as VanillaMinecraftEntry;
+ inheritedEntry = minecraftEntries?.FirstOrDefault(i => i is VanillaMinecraftEntry v && v.Version.VersionId == inheritedInstanceId) as VanillaMinecraftEntry;
if (inheritedEntry is not null) {
foundInheritedInstanceInParsed = true;
diff --git a/MinecraftLaunch/Components/Provider/CurseforgeProvider.cs b/MinecraftLaunch/Components/Provider/CurseforgeProvider.cs
index 3d05c2b..8d58775 100644
--- a/MinecraftLaunch/Components/Provider/CurseforgeProvider.cs
+++ b/MinecraftLaunch/Components/Provider/CurseforgeProvider.cs
@@ -1,4 +1,3 @@
-using System.Diagnostics;
using Flurl;
using Flurl.Http;
using MinecraftLaunch.Base.Enums;
@@ -7,9 +6,9 @@
using MinecraftLaunch.Utilities;
using System.Net.Http.Json;
using System.Text.Json;
-using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using System.Web;
+using MinecraftLaunch.Base.Models.SHA1;
namespace MinecraftLaunch.Components.Provider;
@@ -26,7 +25,7 @@ public async Task> GetFeaturedResourcesAsync(Can
using var doc = await JsonDocument.ParseAsync(stream, cancellationToken: cancellationToken);
var dataElement = doc.RootElement.GetProperty("data"u8);
- var popular = dataElement.GetPropertyNullable("popular");
- var featured = dataElement.GetPropertyNullable("featured");
+ var popular = dataElement.GetPropertyNullable("popular"u8);
+ var featured = dataElement.GetPropertyNullable("featured"u8);
if (popular is null || featured is null) return [];
@@ -294,14 +293,16 @@ static Dictionary ProvideDependencies(JsonElement dependenc
x => (DependencyType)x.GetProperty("relationType"u8).GetInt32()
);
}
- static string ProvideSha(JsonElement hashesArrayNode)
+ static Sha1Data? ProvideSha(JsonElement hashesArrayNode)
{
foreach (var node in hashesArrayNode.EnumerateArray())
{
+
if (!node.TryGetProperty("algo"u8,out var algoElement))continue;
- if (algoElement.GetInt32() is 1) return node.GetProperty("value"u8).GetString();
+ if (algoElement.GetInt32() is 1) return node.GetProperty("value"u8).Deserialize(Sha1Data.Sha1DataSerializerContext.Default.Sha1Data);
}
- return string.Empty;
+
+ return null;
}
}
diff --git a/MinecraftLaunch/Components/Provider/ModrinthProvider.cs b/MinecraftLaunch/Components/Provider/ModrinthProvider.cs
index e5c6cbe..1bc2433 100644
--- a/MinecraftLaunch/Components/Provider/ModrinthProvider.cs
+++ b/MinecraftLaunch/Components/Provider/ModrinthProvider.cs
@@ -9,6 +9,7 @@
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
+using MinecraftLaunch.Base.Models.SHA1;
namespace MinecraftLaunch.Components.Provider;
@@ -235,7 +236,7 @@ private static ModrinthResourceFile ParseFile(JsonElement node)
IsPrimary = primaryFileNode.GetProperty("primary"u8).GetBoolean(),
FileName = primaryFileNode.GetProperty("filename"u8).GetString(),
FileSize = primaryFileNode.GetProperty("size"u8).GetInt64(),
- Sha1 = primaryFileNode.GetProperty("hashes"u8).GetProperty("sha1"u8).GetString(),
+ Sha1 = primaryFileNode.GetProperty("hashes"u8).GetProperty("sha1"u8).Deserialize(Sha1Data.Sha1DataSerializerContext.Default.Sha1Data),
Sha512 = primaryFileNode.GetProperty("hashes"u8).GetProperty("sha512"u8).GetString(),
ReleaseType = node.GetProperty("version_type"u8).GetString() switch
diff --git a/MinecraftLaunch/DownloadManager.cs b/MinecraftLaunch/DownloadManager.cs
index a73a337..017a9d9 100644
--- a/MinecraftLaunch/DownloadManager.cs
+++ b/MinecraftLaunch/DownloadManager.cs
@@ -35,7 +35,7 @@ public string TryFindUrl(string sourceUrl) {
return sourceUrl;
foreach (var (src, mirror) in _replacementMap)
- if (sourceUrl.StartsWith(src))
+ if (sourceUrl.StartsWith(src, StringComparison.Ordinal))
return sourceUrl.Replace(src, mirror);
return sourceUrl;
diff --git a/MinecraftLaunch/Extensions/MinecraftEntryExtension.cs b/MinecraftLaunch/Extensions/MinecraftEntryExtension.cs
index 2cc47ea..ae71d8f 100644
--- a/MinecraftLaunch/Extensions/MinecraftEntryExtension.cs
+++ b/MinecraftLaunch/Extensions/MinecraftEntryExtension.cs
@@ -3,6 +3,7 @@
using MinecraftLaunch.Base.Utilities;
using System.IO.Compression;
using System.Text.Json;
+using MinecraftLaunch.Base.Models.SHA1;
namespace MinecraftLaunch.Extensions;
@@ -56,7 +57,7 @@ public static MinecraftClient GetJarElement(this MinecraftEntry entry) {
long size = clientArtifactNode.GetProperty("size"u8).GetInt64();
string url = clientArtifactNode.GetProperty("url"u8).GetString();
- string sha1 = clientArtifactNode.GetProperty("sha1"u8).GetString();
+ var sha1 = clientArtifactNode.GetPropertyNullable("sha1"u8)?.Deserialize(Sha1Data.Sha1DataSerializerContext.Default.Sha1Data);
if (sha1 is null || url is null)
throw new InvalidDataException("Invalid client info");
@@ -65,7 +66,7 @@ public static MinecraftClient GetJarElement(this MinecraftEntry entry) {
MinecraftFolderPath = entry.MinecraftFolderPath,
ClientId = Path.GetFileNameWithoutExtension(clientJarPath),
Url = url,
- Sha1 = sha1,
+ Sha1 = sha1.Value,
Size = size
};
}
@@ -86,7 +87,7 @@ public static AssstIndex GetAssetIndex(this MinecraftEntry minecraftEntry) {
long size = assetIndex.GetProperty("size"u8).GetInt64();
string id = assetIndex.GetProperty("id"u8).GetString() ?? throw new InvalidDataException();
string url = assetIndex.GetProperty("url"u8).GetString() ?? throw new InvalidDataException();
- string sha1 = assetIndex.GetProperty("sha1"u8).GetString() ?? throw new InvalidDataException();
+ var sha1 = assetIndex.GetPropertyNullable("sha1"u8)?.Deserialize(Sha1Data.Sha1DataSerializerContext.Default.Sha1Data) ?? throw new InvalidDataException();
return new AssstIndex {
Id = id,