diff --git a/ProjBobcat/ProjBobcat/Class/Helper/CurseForgeAPIHelper.cs b/ProjBobcat/ProjBobcat/Class/Helper/CurseForgeAPIHelper.cs index 9d39a77e..6a84acce 100644 --- a/ProjBobcat/ProjBobcat/Class/Helper/CurseForgeAPIHelper.cs +++ b/ProjBobcat/ProjBobcat/Class/Helper/CurseForgeAPIHelper.cs @@ -103,6 +103,8 @@ public static void SetApiKey(string apiKey) using var req = Req(HttpMethod.Get, reqUrl); using var res = await Client.SendAsync(req); + res.EnsureSuccessStatusCode(); + return (await res.Content.ReadFromJsonAsync(CurseForgeModelContext.Default .DataModelCurseForgeLatestFileModelArray))?.Data; } @@ -174,11 +176,11 @@ public static void SetApiKey(string apiKey) return (await res.Content.ReadFromJsonAsync(CurseForgeModelContext.Default.DataModelString))?.Data; } - public static async Task TryFuzzySearchFile(long fingerprint, int gameId = 432) + public static async Task TryFuzzySearchFile(long[] fingerprint, int gameId = 432) { var reqUrl = $"{BaseUrl}/fingerprints/{gameId}"; - var data = JsonSerializer.Serialize(new FuzzyFingerPrintReqModel([fingerprint]), + var data = JsonSerializer.Serialize(new FuzzyFingerPrintReqModel(fingerprint), CurseForgeModelContext.Default.FuzzyFingerPrintReqModel); using var req = Req(HttpMethod.Post, reqUrl); diff --git a/ProjBobcat/ProjBobcat/Class/Helper/GameResourcesResolveHelper.cs b/ProjBobcat/ProjBobcat/Class/Helper/GameResourcesResolveHelper.cs index be1204c0..c47ca55e 100644 --- a/ProjBobcat/ProjBobcat/Class/Helper/GameResourcesResolveHelper.cs +++ b/ProjBobcat/ProjBobcat/Class/Helper/GameResourcesResolveHelper.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using ProjBobcat.Class.Helper.TOMLParser; +using ProjBobcat.Class.Model; using ProjBobcat.Class.Model.Fabric; using ProjBobcat.Class.Model.GameResource; using ProjBobcat.Class.Model.GameResource.ResolvedInfo; @@ -143,6 +144,28 @@ static async Task GetFabricModInfo( } } + public static ModLoaderType GetModLoaderType(IArchive archive) + { + var fabricEntry = archive.Entries.Any(e => + e.Key.EndsWith("fabric.mod.json", StringComparison.OrdinalIgnoreCase)); + + if (fabricEntry) return ModLoaderType.Fabric; + + var neoforgeEntry = archive.Entries.Any(e => + e.Key.EndsWith("_neoforge.mixins.json", StringComparison.OrdinalIgnoreCase)); + + if (neoforgeEntry) return ModLoaderType.NeoForge; + + var forgeEntry = archive.Entries.Any(e => + e.Key.EndsWith("META-INF/mods.toml", StringComparison.OrdinalIgnoreCase)); + var forgeNewEntry = archive.Entries.Any(e => + e.Key.EndsWith("mcmod.info", StringComparison.OrdinalIgnoreCase)); + + if (forgeEntry || forgeNewEntry) return ModLoaderType.Forge; + + return ModLoaderType.Unknown; + } + public static async IAsyncEnumerable ResolveModListAsync( IEnumerable files, [EnumeratorCancellation] CancellationToken ct) @@ -174,23 +197,31 @@ public static async IAsyncEnumerable ResolveModListAsync( GameModResolvedInfo? result = null; - if (modInfoEntry != null) + try { - result = await GetNewModInfo(modInfoEntry, file, isEnabled, ct); - - if (result != null) goto ReturnResult; - } + if (modInfoEntry != null) + { + result = await GetNewModInfo(modInfoEntry, file, isEnabled, ct); - if (tomlInfoEntry != null) - { - result = await GetLegacyModInfo(tomlInfoEntry, file, isEnabled); + if (result != null) goto ReturnResult; + } - if (result != null) goto ReturnResult; - } + if (tomlInfoEntry != null) + { + result = await GetLegacyModInfo(tomlInfoEntry, file, isEnabled); + + if (result != null) goto ReturnResult; + } - if (fabricModInfoEntry != null) + if (fabricModInfoEntry != null) + { + result = await GetFabricModInfo(fabricModInfoEntry, file, isEnabled, ct); + goto ReturnResult; + } + } + catch (Exception e) { - result = await GetFabricModInfo(fabricModInfoEntry, file, isEnabled, ct); + Console.WriteLine(e); goto ReturnResult; } @@ -202,8 +233,9 @@ public static async IAsyncEnumerable ResolveModListAsync( null, "Unknown", isEnabled); - + ReturnResult: + result = result! with { LoaderType = GetModLoaderType(archive) }; yield return result; } } diff --git a/ProjBobcat/ProjBobcat/Class/Helper/ModrinthAPIHelper.cs b/ProjBobcat/ProjBobcat/Class/Helper/ModrinthAPIHelper.cs index 00c5a8f8..a9be1e74 100644 --- a/ProjBobcat/ProjBobcat/Class/Helper/ModrinthAPIHelper.cs +++ b/ProjBobcat/ProjBobcat/Class/Helper/ModrinthAPIHelper.cs @@ -1,6 +1,10 @@ using System; +using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Json; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using ProjBobcat.Class.Model; @@ -8,6 +12,15 @@ namespace ProjBobcat.Class.Helper; +#region Temp Models + +record FileMatchRequestModel(string[] hashes, string algorithm, string[] loaders, string[] game_versions); + +[JsonSerializable(typeof(FileMatchRequestModel))] +partial class ModrinthModelContext : JsonSerializerContext; + +#endregion + public static class ModrinthAPIHelper { const string BaseUrl = "https://api.modrinth.com/v2"; @@ -110,4 +123,34 @@ await res.Content.ReadFromJsonAsync(ModrinthProjectDependencyInfoContext.Default return resModel; } + + public static async Task GetVersionInfo(string versionId) + { + var reqUrl = $"{BaseUrl}/version/{versionId}"; + + using var res = await Get(reqUrl); + var resModel = await res.Content.ReadFromJsonAsync(ModrinthVersionInfoContext.Default.ModrinthVersionInfo); + + return resModel; + } + + public static async Task?> TryMatchFile( + string[] hashes, + string algorithm, + string[] loaders, + string[] game_versions) + { + const string reqUrl = $"{BaseUrl}/version_files/update"; + + var data = JsonSerializer.Serialize(new FileMatchRequestModel(hashes, algorithm, loaders, game_versions), + ModrinthModelContext.Default.FileMatchRequestModel); + + using var res = await HttpHelper.Post(reqUrl, data); + + + if (!res.IsSuccessStatusCode) return null; + + return await res.Content.ReadFromJsonAsync(ModrinthVersionInfoContext.Default + .IReadOnlyDictionaryStringModrinthVersionInfo); + } } \ No newline at end of file diff --git a/ProjBobcat/ProjBobcat/Class/Model/CurseForge/CurseForgeLatestFileModel.cs b/ProjBobcat/ProjBobcat/Class/Model/CurseForge/CurseForgeLatestFileModel.cs index b87f09e8..7c7d1868 100644 --- a/ProjBobcat/ProjBobcat/Class/Model/CurseForge/CurseForgeLatestFileModel.cs +++ b/ProjBobcat/ProjBobcat/Class/Model/CurseForge/CurseForgeLatestFileModel.cs @@ -4,9 +4,15 @@ namespace ProjBobcat.Class.Model.CurseForge; +public class CurseForgeFileHashModel +{ + [JsonPropertyName("algo")] public int Algorithm { get; init; } + [JsonPropertyName("value")] public string? Value { get; init; } +} + public class CurseForgeLatestFileModel { - [JsonPropertyName("id")] public int Id { get; set; } + [JsonPropertyName("id")] public long Id { get; set; } [JsonPropertyName("displayName")] public required string DisplayName { get; init; } @@ -28,6 +34,8 @@ public class CurseForgeLatestFileModel [JsonPropertyName("dependencies")] public CurseForgeDependencyModel[]? Dependencies { get; set; } + [JsonPropertyName("hashes")] public CurseForgeFileHashModel[]? Hashes { get; set; } + [JsonPropertyName("isAvailable")] public bool IsAvailable { get; set; } [JsonPropertyName("modules")] public CurseForgeModuleModel[]? Modules { get; set; } @@ -35,6 +43,9 @@ public class CurseForgeLatestFileModel [JsonPropertyName("packageFingerprint")] public long PackageFingerprint { get; set; } + [JsonPropertyName("fileFingerprint")] + public long FileFingerprint { get; set; } + [JsonPropertyName("gameVersions")] public required string[] GameVersions { get; init; } [JsonPropertyName("sortableGameVersion")] diff --git a/ProjBobcat/ProjBobcat/Class/Model/DownloadSettings.cs b/ProjBobcat/ProjBobcat/Class/Model/DownloadSettings.cs index 34c234d2..ea941112 100644 --- a/ProjBobcat/ProjBobcat/Class/Model/DownloadSettings.cs +++ b/ProjBobcat/ProjBobcat/Class/Model/DownloadSettings.cs @@ -42,7 +42,7 @@ public class DownloadSettings /// public string? Host { get; init; } - public async ValueTask HashDataAsync(Stream stream, CancellationToken? token) + public async Task HashDataAsync(Stream stream, CancellationToken? token) { token ??= CancellationToken.None; diff --git a/ProjBobcat/ProjBobcat/Class/Model/GameResource/ResolvedInfo/GameModResolvedInfo.cs b/ProjBobcat/ProjBobcat/Class/Model/GameResource/ResolvedInfo/GameModResolvedInfo.cs index 1629a24e..35132526 100644 --- a/ProjBobcat/ProjBobcat/Class/Model/GameResource/ResolvedInfo/GameModResolvedInfo.cs +++ b/ProjBobcat/ProjBobcat/Class/Model/GameResource/ResolvedInfo/GameModResolvedInfo.cs @@ -9,4 +9,7 @@ public record GameModResolvedInfo( string? Title, string? Version, string? ModType, - bool IsEnabled); \ No newline at end of file + bool IsEnabled) +{ + public ModLoaderType LoaderType { get; init; } +} \ No newline at end of file diff --git a/ProjBobcat/ProjBobcat/Class/Model/ModLoaderType.cs b/ProjBobcat/ProjBobcat/Class/Model/ModLoaderType.cs new file mode 100644 index 00000000..5c6864bb --- /dev/null +++ b/ProjBobcat/ProjBobcat/Class/Model/ModLoaderType.cs @@ -0,0 +1,10 @@ +namespace ProjBobcat.Class.Model; + +public enum ModLoaderType +{ + Forge, + NeoForge, + Fabric, + Quilt, + Unknown +} \ No newline at end of file diff --git a/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthCategoryInfo.cs b/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthCategoryInfo.cs index d0db936d..801a05fb 100644 --- a/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthCategoryInfo.cs +++ b/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthCategoryInfo.cs @@ -13,6 +13,4 @@ public class ModrinthCategoryInfo [JsonSerializable(typeof(ModrinthCategoryInfo))] [JsonSerializable(typeof(ModrinthCategoryInfo[]))] -partial class ModrinthCategoryInfoContext : JsonSerializerContext -{ -} \ No newline at end of file +partial class ModrinthCategoryInfoContext : JsonSerializerContext; \ No newline at end of file diff --git a/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthSearchOptions.cs b/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthSearchOptions.cs index 0fdff53b..99538d50 100644 --- a/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthSearchOptions.cs +++ b/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthSearchOptions.cs @@ -1,4 +1,5 @@ -using System.Text; +using System.Collections.Generic; +using System.Text; namespace ProjBobcat.Class.Model.Modrinth; @@ -7,19 +8,25 @@ public class ModrinthSearchOptions public string? Name { get; init; } public string? Category { get; init; } public string Index { get; init; } = "relevance"; - public string? ProjectType { get; init; } = "mod"; + public string? ProjectType { get; init; } public int? Offset { get; init; } public int? Limit { get; set; } public override string ToString() { - var sb = new StringBuilder($"?query={Name ?? "any"}&index={Index}&facets=["); - var projType = $"[\"project_type:{ProjectType}\"]"; + var sb = new StringBuilder($"?query={Name ?? "any"}&index={Index}"); - if (!string.IsNullOrEmpty(Category)) sb.Append($"[\"categories:{Category}\"],"); + var facets = new List(); - sb.Append(projType); - sb.Append(']'); + if (!string.IsNullOrEmpty(Category)) + facets.Add($"[\"categories:{Category}\"]"); + if (!string.IsNullOrEmpty(ProjectType)) + facets.Add($"[\"project_type:{ProjectType}\"]"); + + if (facets.Count > 0) + sb.Append("&facets=[") + .AppendJoin(',', facets) + .Append(']'); if (Offset != null) sb.Append($"&offset={Offset}"); if (Limit != null) sb.Append($"&limit={Limit}"); diff --git a/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthVersionInfo.cs b/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthVersionInfo.cs index 0c38d72c..1889420b 100644 --- a/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthVersionInfo.cs +++ b/ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthVersionInfo.cs @@ -77,4 +77,5 @@ public class ModrinthVersionInfo [JsonSerializable(typeof(ModrinthVersionInfo))] [JsonSerializable(typeof(ModrinthVersionInfo[]))] +[JsonSerializable(typeof(IReadOnlyDictionary))] partial class ModrinthVersionInfoContext : JsonSerializerContext; \ No newline at end of file diff --git a/ProjBobcat/ProjBobcat/DefaultComponent/Installer/ModPackInstaller/CurseForgeInstaller.cs b/ProjBobcat/ProjBobcat/DefaultComponent/Installer/ModPackInstaller/CurseForgeInstaller.cs index 37b9154a..f8be2f7e 100644 --- a/ProjBobcat/ProjBobcat/DefaultComponent/Installer/ModPackInstaller/CurseForgeInstaller.cs +++ b/ProjBobcat/ProjBobcat/DefaultComponent/Installer/ModPackInstaller/CurseForgeInstaller.cs @@ -28,7 +28,7 @@ public void Install() InstallTaskAsync().Wait(); } - public static async ValueTask<(string? FileName, string? Url)> TryGuessModDownloadLink(long fileId) + public static async Task<(string? FileName, string? Url)> TryGuessModDownloadLink(long fileId) { try { @@ -69,7 +69,7 @@ public void Install() } } - async ValueTask<(bool, DownloadFile?)> TryGuessModDownloadLink(long fileId, string downloadPath) + async Task<(bool, DownloadFile?)> TryGuessModDownloadLink(long fileId, string downloadPath) { var pair = await TryGuessModDownloadLink(fileId); diff --git a/ProjBobcat/ProjBobcat/DefaultComponent/Launch/DefaultVersionLocator.cs b/ProjBobcat/ProjBobcat/DefaultComponent/Launch/DefaultVersionLocator.cs index 3bcb5430..2aac87b2 100644 --- a/ProjBobcat/ProjBobcat/DefaultComponent/Launch/DefaultVersionLocator.cs +++ b/ProjBobcat/ProjBobcat/DefaultComponent/Launch/DefaultVersionLocator.cs @@ -391,7 +391,7 @@ public override (List, List) GetNatives(Library[] libr Logging = rawVersion.Logging, Id = rawVersion.Id, InheritsFrom = rawVersion.InheritsFrom, - GameBaseVersion = GameVersionHelper.TryGetMcVersion([.. (inherits ?? []), rawVersion]) ?? id, + GameBaseVersion = GameVersionHelper.TryGetMcVersion([.. inherits ?? [], rawVersion]) ?? id, DirName = id, Name = id, JavaVersion = rawVersion.JavaVersion, @@ -559,7 +559,7 @@ public override (List, List) GetNatives(Library[] libr } } - rawLibs = NativeReplaceHelper.Replace([rawVersion, ..inherits ?? []], rawLibs, NativeReplacementPolicy); + rawLibs = NativeReplaceHelper.Replace([rawVersion, .. inherits ?? []], rawLibs, NativeReplacementPolicy); var libs = GetNatives([.. rawLibs]); @@ -583,40 +583,28 @@ void ProcessProfile(VersionInfo result, string id) if (LauncherProfileParser == null) return; var gameId = id.ToGuidHash().ToString("N"); - var (oldProfileKey, oldProfileModel) = - LauncherProfileParser.LauncherProfile.Profiles! - .FirstOrDefault(p => p.Key.Equals(gameId, StringComparison.OrdinalIgnoreCase)); - var gamePath = Path.Combine(RootPath, GamePathHelper.GetGamePath(id)); - if (string.IsNullOrEmpty(oldProfileKey) || oldProfileModel == null) + if (LauncherProfileParser.LauncherProfile.Profiles!.TryGetValue(gameId, out var oldProfileModel)) { - var gameProfile = new GameProfileModel - { - GameDir = gamePath, - LastVersionId = id, - Name = id, - Created = DateTime.Now - }; - - if (!string.IsNullOrEmpty(oldProfileKey) && - LauncherProfileParser.LauncherProfile.Profiles!.ContainsKey(oldProfileKey)) - { - LauncherProfileParser.LauncherProfile.Profiles![oldProfileKey] = gameProfile; - LauncherProfileParser.SaveProfile(); - return; - } - - LauncherProfileParser.LauncherProfile.Profiles!.Add(gameId, gameProfile); + result.Name = oldProfileModel.Name!; + oldProfileModel.GameDir = gamePath; + oldProfileModel.LastVersionId = id; + LauncherProfileParser.LauncherProfile.Profiles![gameId] = oldProfileModel; LauncherProfileParser.SaveProfile(); return; } - result.Name = oldProfileModel.Name!; - oldProfileModel.GameDir = gamePath; - oldProfileModel.LastVersionId = id; - LauncherProfileParser.LauncherProfile.Profiles![oldProfileKey] = oldProfileModel; + var gameProfile = new GameProfileModel + { + GameDir = gamePath, + LastVersionId = id, + Name = id, + Created = DateTime.Now + }; + + LauncherProfileParser.LauncherProfile.Profiles!.Add(gameId, gameProfile); LauncherProfileParser.SaveProfile(); } } \ No newline at end of file