Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions IO/NetFile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
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;

public class NetFile
{
public required string Path { get; set; }
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;
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;
}
// 这个方法存在的意义就是为了让 Downloader 支持换源重试
/// <summary>
/// 获取当前对象的 DownloadItem 列表。
/// </summary>
/// <returns></returns>
public List<DownloadItem> GetDownloadItem()
{
var list = new List<DownloadItem>();
foreach (var url in Url)
{
var item = new DownloadItem(url.ToUri(), Path);
item.Finished += () => CheckFile();
list.Add(item);
}
return list;
}
}

public enum HashAlgorithm {
md5,
sha1,
sha256,
sha512
}
46 changes: 46 additions & 0 deletions Minecraft/Instance/Clients/ClientBase.cs
Original file line number Diff line number Diff line change
@@ -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<JsonNode?> 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<IEnumerable<NetFile>> AnalyzeLibraryAsync()
{
throw new NotImplementedException();
}

public virtual Task<IEnumerable<NetFile>> AnalyzeMissingLibraryAsync()
{
throw new NotImplementedException();
}

public virtual Task ExecuteInstallerAsync(string path)
{
throw new NotImplementedException();
}

public virtual Task<string> GetJsonAsync()
{
throw new NotImplementedException();
}
}
6 changes: 6 additions & 0 deletions Minecraft/Instance/Clients/ForgeClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace PCL.Core.Minecraft.Instance.Clients;

public class ForgeClient : ClientBase
{

}
191 changes: 191 additions & 0 deletions Minecraft/Instance/Clients/MinecraftClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
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;
using PCL.Core.Utils.Exts;
using PCL.Core.Minecraft.Instance;
using PCL.Core.IO;

namespace PCL.Core.Minecraft.Instance.Clients;

public class MinecraftClient : ClientBase
{
public static JsonNode? VersionList;
private Version? _version;
private JsonNode? _versionJson;
private string? _jsonUrl;

Check warning on line 25 in Minecraft/Instance/Clients/MinecraftClient.cs

View workflow job for this annotation

GitHub Actions / Build (Debug, x64) / Build

Field 'MinecraftClient._jsonUrl' is never assigned to, and will always have its default value null

Check warning on line 25 in Minecraft/Instance/Clients/MinecraftClient.cs

View workflow job for this annotation

GitHub Actions / Build (Debug, ARM64) / Build

Field 'MinecraftClient._jsonUrl' is never assigned to, and will always have its default value null
private string? _jsonHash;

Check warning on line 26 in Minecraft/Instance/Clients/MinecraftClient.cs

View workflow job for this annotation

GitHub Actions / Build (Debug, x64) / Build

Field 'MinecraftClient._jsonHash' is never assigned to, and will always have its default value null

Check warning on line 26 in Minecraft/Instance/Clients/MinecraftClient.cs

View workflow job for this annotation

GitHub Actions / Build (Debug, ARM64) / Build

Field 'MinecraftClient._jsonHash' is never assigned to, and will always have its default value null
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

Check failure on line 30 in Minecraft/Instance/Clients/MinecraftClient.cs

View workflow job for this annotation

GitHub Actions / Build (Debug, x64) / Build

An object reference is required for the non-static field, method, or property 'Config.ToolConfigGroup.DownloadConfigGroup.VersionSourceSolution'

Check warning on line 30 in Minecraft/Instance/Clients/MinecraftClient.cs

View workflow job for this annotation

GitHub Actions / Build (Debug, x64) / Build

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '3' is not covered.

Check warning on line 30 in Minecraft/Instance/Clients/MinecraftClient.cs

View workflow job for this annotation

GitHub Actions / Build (Debug, x64) / Build

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '3' is not covered.

Check failure on line 30 in Minecraft/Instance/Clients/MinecraftClient.cs

View workflow job for this annotation

GitHub Actions / Build (Debug, x64) / Build

An object reference is required for the non-static field, method, or property 'Config.ToolConfigGroup.DownloadConfigGroup.VersionSourceSolution'

Check failure on line 30 in Minecraft/Instance/Clients/MinecraftClient.cs

View workflow job for this annotation

GitHub Actions / Build (Debug, ARM64) / Build

An object reference is required for the non-static field, method, or property 'Config.ToolConfigGroup.DownloadConfigGroup.VersionSourceSolution'

Check warning on line 30 in Minecraft/Instance/Clients/MinecraftClient.cs

View workflow job for this annotation

GitHub Actions / Build (Debug, ARM64) / Build

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '3' is not covered.

Check warning on line 30 in Minecraft/Instance/Clients/MinecraftClient.cs

View workflow job for this annotation

GitHub Actions / Build (Debug, ARM64) / Build

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '3' is not covered.

Check failure on line 30 in Minecraft/Instance/Clients/MinecraftClient.cs

View workflow job for this annotation

GitHub Actions / Build (Debug, ARM64) / Build

An object reference is required for the non-static field, method, or property 'Config.ToolConfigGroup.DownloadConfigGroup.VersionSourceSolution'
{
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")
.Replace(AssetsBaseUri, "https://bmclapi2.bangbang93.com/assets");
return Config.ToolConfigGroup.DownloadConfigGroup.VersionSourceSolution switch

Check failure on line 43 in Minecraft/Instance/Clients/MinecraftClient.cs

View workflow job for this annotation

GitHub Actions / Build (Debug, x64) / Build

An object reference is required for the non-static field, method, or property 'Config.ToolConfigGroup.DownloadConfigGroup.VersionSourceSolution'

Check warning on line 43 in Minecraft/Instance/Clients/MinecraftClient.cs

View workflow job for this annotation

GitHub Actions / Build (Debug, x64) / Build

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '3' is not covered.

Check warning on line 43 in Minecraft/Instance/Clients/MinecraftClient.cs

View workflow job for this annotation

GitHub Actions / Build (Debug, x64) / Build

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '3' is not covered.

Check failure on line 43 in Minecraft/Instance/Clients/MinecraftClient.cs

View workflow job for this annotation

GitHub Actions / Build (Debug, x64) / Build

An object reference is required for the non-static field, method, or property 'Config.ToolConfigGroup.DownloadConfigGroup.VersionSourceSolution'

Check failure on line 43 in Minecraft/Instance/Clients/MinecraftClient.cs

View workflow job for this annotation

GitHub Actions / Build (Debug, ARM64) / Build

An object reference is required for the non-static field, method, or property 'Config.ToolConfigGroup.DownloadConfigGroup.VersionSourceSolution'

Check warning on line 43 in Minecraft/Instance/Clients/MinecraftClient.cs

View workflow job for this annotation

GitHub Actions / Build (Debug, ARM64) / Build

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '3' is not covered.

Check warning on line 43 in Minecraft/Instance/Clients/MinecraftClient.cs

View workflow job for this annotation

GitHub Actions / Build (Debug, ARM64) / Build

The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '3' is not covered.

Check failure on line 43 in Minecraft/Instance/Clients/MinecraftClient.cs

View workflow job for this annotation

GitHub Actions / Build (Debug, ARM64) / Build

An object reference is required for the non-static field, method, or property 'Config.ToolConfigGroup.DownloadConfigGroup.VersionSourceSolution'
{
0 => [uri, uri, mirror],
1 => [uri, mirror, uri],
2 => [mirror, mirror, uri]
};
}

public new static async Task<JsonNode?> GetVersionInfoAsync(string mcVersion)
{
if (VersionList is null) await UpdateVersionIndexAsync();
return VersionList!["versions"]?.AsArray().Where(value => value?["id"]?.ToString() == mcVersion).First();
}
public new 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<JsonNode>();
}
catch (HttpRequestException ex)
{
LogWrapper.Error(ex, "Minecraft", "Failed to get version list");
}
}
}
public override async Task<string> GetJsonAsync()
{

foreach (var source in _GetFileSource(_jsonUrl!.ToString()))
{
try
{
var response = await HttpRequestBuilder.Create(source, HttpMethod.Get).SendAsync(true);
var content = await response.AsStringAsync();
if (!string.IsNullOrEmpty(_jsonHash))
{
var hashResult = SHA1Provider.Instance.ComputeHash(content);
if (string.Equals(hashResult, _jsonHash, StringComparison.OrdinalIgnoreCase)) continue;
}
return content;
}
catch (HttpRequestException ex)
{
LogWrapper.Error(ex, "Minecraft", "下载版本 Json 失败");
}
}
throw new HttpRequestException("Failed to download version json:All of source unavailable");
}
public override async Task<IEnumerable<NetFile>> AnalyzeLibraryAsync()
{
var list = new List<NetFile>();
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
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(new NetFile()
{
Path = "",
Url = [""],
Size = 0,
Algorithm = HashAlgorithm.sha1,
Hash = ""
});
}
if (classifiers is not null)
{
// get key by os type
var nativeKey = library?["natives"]?[Environment.OSVersion.Platform.ToString()]?.ToString();
if (string.IsNullOrEmpty(nativeKey)) continue;
if (nativeKey.Contains("arch"))
nativeKey = nativeKey.Replace("${arch}", $"{(RuntimeInformation.OSArchitecture == Architecture.X86 ? "86" : "64")}");

}
}
return list;
}
public override Task<IEnumerable<NetFile>> AnalyzeMissingLibraryAsync()
{
return base.AnalyzeMissingLibraryAsync();
}
public async Task<List<NetFile>> AnalyzeAssetsAsync(JsonNode versionJson)
{
await GetJsonAsync();
var list = new List<NetFile>();
foreach (var asset in versionJson["object"]!.AsObject())
{
var hash = asset!.Value!["hash"]!.ToString();
var size = asset!.Value!["size"]!.GetValue<int>();
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<MinecraftClient> ParseAsync<T>(string mcVersion)
{
if (VersionList is null) await UpdateVersionIndexAsync();
var info = GetVersionInfoAsync(mcVersion);
var client = new MinecraftClient()
{
_version = new Version(mcVersion)
};
return client;
}
public override async Task ExecuteInstallerAsync(string path)

Check warning on line 187 in Minecraft/Instance/Clients/MinecraftClient.cs

View workflow job for this annotation

GitHub Actions / Build (Debug, x64) / Build

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 187 in Minecraft/Instance/Clients/MinecraftClient.cs

View workflow job for this annotation

GitHub Actions / Build (Debug, x64) / Build

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 187 in Minecraft/Instance/Clients/MinecraftClient.cs

View workflow job for this annotation

GitHub Actions / Build (Debug, ARM64) / Build

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 187 in Minecraft/Instance/Clients/MinecraftClient.cs

View workflow job for this annotation

GitHub Actions / Build (Debug, ARM64) / Build

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{

}
}
18 changes: 18 additions & 0 deletions Minecraft/Instance/IClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Threading.Tasks;
using PCL.Core.Net;
using System.Text.Json.Nodes;
using System.Collections.Generic;
using PCL.Core.IO;

namespace PCL.Core.Minecraft.Instance;

public interface IClient
{
static abstract Task<JsonNode?> GetVersionInfoAsync(string version);
static abstract Task UpdateVersionIndexAsync();
abstract Task<IEnumerable<NetFile>> AnalyzeLibraryAsync();
abstract Task<string> GetJsonAsync();
static abstract Task ParseAsync(string version);
abstract Task ExecuteInstallerAsync(string path);
abstract Task<IEnumerable<NetFile>> AnalyzeMissingLibraryAsync();
}
1 change: 1 addition & 0 deletions Minecraft/Instance/IVersion.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
namespace PCL.Core.Minecraft.Instance.Clients;
17 changes: 17 additions & 0 deletions Minecraft/Instance/InstanceInstallHandler.cs
Original file line number Diff line number Diff line change
@@ -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<ClientBase> clients,string path)

Check warning on line 9 in Minecraft/Instance/InstanceInstallHandler.cs

View workflow job for this annotation

GitHub Actions / Build (Debug, x64) / Build

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 9 in Minecraft/Instance/InstanceInstallHandler.cs

View workflow job for this annotation

GitHub Actions / Build (Debug, x64) / Build

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 9 in Minecraft/Instance/InstanceInstallHandler.cs

View workflow job for this annotation

GitHub Actions / Build (Debug, ARM64) / Build

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 9 in Minecraft/Instance/InstanceInstallHandler.cs

View workflow job for this annotation

GitHub Actions / Build (Debug, ARM64) / Build

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{
var task = new List<Task>();
foreach (var client in clients){
task.Add(client.GetJsonAsync());
}
}

}
43 changes: 43 additions & 0 deletions Minecraft/JavaHelper.cs
Original file line number Diff line number Diff line change
@@ -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<JsonNode>();
}
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;
}
}
Loading
Loading