diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..013007bb
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "dotnet.preferCSharpExtension": true
+}
\ No newline at end of file
diff --git a/PCL.Core.csproj b/PCL.Core.csproj
index b130a4e5..6b49118b 100644
--- a/PCL.Core.csproj
+++ b/PCL.Core.csproj
@@ -171,6 +171,10 @@
+
+
+
+
diff --git a/PCL.Core.sln b/PCL.Core.sln
new file mode 100644
index 00000000..16a96477
--- /dev/null
+++ b/PCL.Core.sln
@@ -0,0 +1,24 @@
+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
+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
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {E1C68EDF-2735-4241-A0AE-513B3C1AC497}
+ EndGlobalSection
+EndGlobal
diff --git a/Utils/Mod/CurseForge.cs b/Utils/Mod/CurseForge.cs
new file mode 100644
index 00000000..a04de201
--- /dev/null
+++ b/Utils/Mod/CurseForge.cs
@@ -0,0 +1,33 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Text.Json;
+using System.Text.Json.Nodes;
+using System.Threading.Tasks;
+
+namespace PCL.Core.Utils.Mod;
+
+public class CurseForgeMod
+{
+ private static string CurseForgeBaseAPI = "https://api.curseforge.com/v1";
+
+ public static async Task GetModInfomationByHash(List modHash)
+ {
+ HttpResponseMessage result = await Network.GetResponse(
+ CurseForgeBaseAPI + "/fingerprints/432",
+ HttpMethod.Post,
+ new Dictionary()
+ {
+ ["Content-Type"] = "application/json"
+ },
+ new JsonObject()
+ {
+ ["fingerprints"] = JsonSerializer.Serialize(modHash)
+ }.ToJsonString());
+
+
+
+ return JsonNode.Parse(await result.Content.ReadAsStringAsync());
+ }
+
+}
\ No newline at end of file
diff --git a/Utils/Modpack/CurseForge.cs b/Utils/Modpack/CurseForge.cs
new file mode 100644
index 00000000..acb51e1b
--- /dev/null
+++ b/Utils/Modpack/CurseForge.cs
@@ -0,0 +1,152 @@
+
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text.Json;
+using System.Text.Json.Nodes;
+using System.Threading.Tasks;
+using PCL.Core.Utils.Mod;
+
+namespace PCL.Core.Utils.Modpack;
+
+
+public class CurseForge
+{
+ public string? Author;
+ public string? Name;
+ public string? Version;
+ public string? InputFolder;
+ public string? OutputFile;
+ public List Modloaders = new();
+ private List ModFiles = new();
+ private JsonNode? Manifest;
+ private List AddToFile = new();
+ private dynamic? Entry;
+ private void OnFinsh()
+ {
+ Task.Run(() =>
+ {
+ this.Export();
+ });
+ }
+
+ private void Start()
+ {
+ List tasks = [
+ this.PrepareMods(),
+ this.PrepareOverride()
+ ];
+ Task.WaitAll(tasks.ToArray());
+ this.PrepareJson().GetAwaiter().GetResult();
+ this.OnFinsh();
+ }
+
+ private void Export()
+ {
+ if(this.OutputFile is null) throw new ArgumentNullException("参数无效:OutputFile");
+ using (FileStream fs = new(this.OutputFile, FileMode.Create))
+ {
+ using (ZipArchive Zipfile = new(fs))
+ {
+ using(var Entry = Zipfile.CreateEntry("manifest.json").Open())
+ {
+ using (MemoryStream memoryStream = new(this.Manifest!.ToJsonString().GetBytes()))
+ {
+ memoryStream.CopyTo(Entry);
+ }
+ }
+
+
+
+ }
+ }
+ }
+
+ public async Task PrepareOverride()
+ {
+ foreach (var (file,Entry) in this.ModFiles.Zip(this.Entry, (f,e) => (f,e)))
+ {
+ if(file.FileId == Entry.FileId) continue;
+ }
+ foreach (var file in Directory.GetFiles(this.InputFolder))
+ {
+
+ }
+ return true;
+ }
+ public async Task PrepareJson()
+ {
+ this.Manifest = new JsonObject()
+ {
+ ["minecraft"] = new JsonObject()
+ {
+ ["version"] = this.Version,
+ ["modloaders"] = JsonSerializer.Serialize(this.Modloaders)
+ },
+ ["manifestType"] = "minecraftModpack",
+ ["overrides"] = "overrides",
+ ["manifestVersion"] = 1,
+ ["version"] = this.Version,
+ ["author"] = this.Author,
+ ["name"] = this.Name,
+ ["files"] = JsonSerializer.Serialize(this.ModFiles)
+ };
+ }
+ public async Task PrepareMods(dynamic? modHashes = null)
+ {
+ if (modHashes is null) goto ModCompareFinished;
+ this.Entry = modHashes;
+ modHashes["modloaders"] = JsonSerializer.Serialize(this.Modloaders);
+ string modsFolder = InputFolder?.TrimEnd('/') + "/mods";
+ if (Directory.Exists(modsFolder))
+ {
+ List modHash = new();
+ JsonNode? result = await CurseForgeMod.GetModInfomationByHash(modHash);
+ if (result is null) goto ModCompareFinished;
+ JsonArray? data = result["data"]?["exactMatches"]?.AsObject().AsArray();
+ if (data is null) goto ModCompareFinished;
+ foreach (JsonNode? modInfo in data)
+ {
+ try
+ {
+ ModFiles.Add(new CfModFile()
+ {
+ ProjectId = int.Parse(data["id"]?.ToString()),
+ FileId = int.Parse(data["file"]?["id"]?.ToString()),
+ Required = true,
+ });
+ }
+ catch
+ {
+ continue;
+ }
+ }
+ }
+ ModCompareFinished:
+ return true;
+ }
+}
+
+
+public class CfPackVersion
+{
+ public string Version;
+ public List Modloaders;
+}
+
+public class CfModloaders
+{
+ public string? Id;
+ public bool Primary;
+}
+
+public class CfModFile
+{
+ public int ProjectId;
+ public int FileId;
+ public bool Required;
+}
\ No newline at end of file
diff --git a/Utils/Network.cs b/Utils/Network.cs
new file mode 100644
index 00000000..3987b758
--- /dev/null
+++ b/Utils/Network.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Http;
+using System.Runtime.Remoting.Messaging;
+using System.Threading;
+using System.Threading.Tasks;
+using PCL.Core.Utils;
+namespace PCL.Core.Utils;
+
+public class Network
+{
+ private static readonly HttpClientHandler Handler = new()
+ {
+ AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip | DecompressionMethods.None
+ };
+ private static readonly HttpClient Client = new(Handler);
+
+ public static async Task GetResponse(
+ string requestUrl,
+ HttpMethod method,
+ Dictionary? headers = null,
+ object? data = null,
+ int timeout = 25000
+ )
+ {
+ int retry = 3;
+ retry:
+ using (HttpRequestMessage request = new(method,requestUrl))
+ {
+ if (data is not null)
+ request.Content = new ByteArrayContent((data is string)? (data as string).GetBytes():data as byte[]);
+ if (headers is not null) foreach (var header in headers)
+ {
+ if (header.Key.StartsWith("Content", StringComparison.OrdinalIgnoreCase) && request.Content is not null)
+ {
+ request.Content.Headers.TryAddWithoutValidation(header.Key, header.Value);
+ continue;
+ }
+
+ request.Headers.TryAddWithoutValidation(header.Key, header.Value);
+ }
+
+ using (CancellationTokenSource cts = new(timeout))
+ {
+ HttpResponseMessage response =
+ await Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cts.Token);
+ try
+ {
+ response.EnsureSuccessStatusCode();
+ return response;
+ }
+ catch (HttpRequestException ex)
+ {
+ if (retry > 0)
+ {
+ retry--;
+ goto retry;
+ }
+ // 尝试读取远程服务器返回的数据数据
+ try
+ {
+ if (response.Content.Headers.ContentLength <= 1024 * 1024)
+ ex.Data["ServerResponse"] = (await response.Content.ReadAsByteArrayAsync()).GetString();
+ }
+ catch
+ {
+
+ }
+ throw;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Utils/Override.cs b/Utils/Override.cs
new file mode 100644
index 00000000..93c22806
--- /dev/null
+++ b/Utils/Override.cs
@@ -0,0 +1,14 @@
+namespace PCL.Core.Utils;
+using System.Text;
+
+public static class Override
+{
+ public static byte[] GetBytes(this string content,Encoding? encode = null)
+ {
+ return (encode ?? Encoding.UTF8).GetBytes(content);
+ }
+ public static string GetString(this byte[] content,Encoding? encode = null)
+ {
+ return (encode ?? Encoding.UTF8).GetString(content);
+ }
+}
\ No newline at end of file