-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add support for Modrinth Mod Pack installation
- Loading branch information
1 parent
20c45ab
commit 20cbb56
Showing
9 changed files
with
293 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
using System; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Threading.Tasks; | ||
using ProjBobcat.Class.Model; | ||
using SharpCompress.Archives; | ||
|
||
namespace ProjBobcat.Class.Helper; | ||
|
||
public static class FileTypeHelper | ||
{ | ||
public static async Task<AssetFileType> TryDetectFileTypeAsync(string filePath) | ||
{ | ||
if(!File.Exists(filePath)) throw new IOException("File not found."); | ||
|
||
var extension = Path.GetExtension(filePath); | ||
|
||
switch (extension) | ||
{ | ||
case ".mrpack": | ||
return AssetFileType.ModrinthModPack; | ||
case ".zip": | ||
{ | ||
await using var fs = File.OpenRead(filePath); | ||
using var archive = ArchiveFactory.Open(fs); | ||
|
||
if(archive.Entries.Any(e => e.Key.Equals("manifest.json", StringComparison.OrdinalIgnoreCase))) return AssetFileType.CurseForgeModPack; | ||
if(archive.Entries.Any(e => e.Key.Equals("modrinth.index.json", StringComparison.OrdinalIgnoreCase))) return AssetFileType.ModrinthModPack; | ||
break; | ||
} | ||
} | ||
|
||
return AssetFileType.Unknown; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
namespace ProjBobcat.Class.Model; | ||
|
||
public enum AssetFileType | ||
{ | ||
CurseForgeModPack, | ||
ModrinthModPack, | ||
Unknown | ||
} |
19 changes: 19 additions & 0 deletions
19
ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthModPackFileModel.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
using System.Collections.Generic; | ||
using System.Text.Json.Serialization; | ||
|
||
namespace ProjBobcat.Class.Model.Modrinth; | ||
|
||
public class ModrinthModPackFileModel | ||
{ | ||
[JsonPropertyName("path")] | ||
public string? Path { get; set; } | ||
|
||
[JsonPropertyName("hashes")] | ||
public Dictionary<string, string> Hashes { get; set; } | ||
|
||
[JsonPropertyName("downloads")] | ||
public string[] Downloads { get; set; } | ||
|
||
[JsonPropertyName("fileSize")] | ||
public long Size { get; set; } | ||
} |
34 changes: 34 additions & 0 deletions
34
ProjBobcat/ProjBobcat/Class/Model/Modrinth/ModrinthModPackIndexModel.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
using System.Collections.Generic; | ||
using System.Text.Json.Serialization; | ||
|
||
namespace ProjBobcat.Class.Model.Modrinth; | ||
|
||
public class ModrinthModPackIndexModel | ||
{ | ||
[JsonPropertyName("formatVersion")] | ||
public int FormatVersion { get; set; } | ||
|
||
[JsonPropertyName("game")] | ||
public string? Game { get; set; } | ||
|
||
[JsonPropertyName("versionId")] | ||
public string? VersionId { get; set; } | ||
|
||
[JsonPropertyName("name")] | ||
public string? Name { get; set; } | ||
|
||
[JsonPropertyName("summary")] | ||
public string? Summary { get; set; } | ||
|
||
[JsonPropertyName("files")] | ||
public ModrinthModPackFileModel[] Files { get; set; } | ||
|
||
[JsonPropertyName("dependencies")] | ||
public Dictionary<string, string> Dependencies { get; set; } | ||
} | ||
|
||
[JsonSerializable(typeof(ModrinthModPackIndexModel))] | ||
partial class ModrinthModPackIndexModelContext : JsonSerializerContext | ||
{ | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
29 changes: 29 additions & 0 deletions
29
ProjBobcat/ProjBobcat/DefaultComponent/Installer/ModPackInstaller/ModPackInstallerBase.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
using System.Collections.Concurrent; | ||
using ProjBobcat.Class.Model; | ||
using ProjBobcat.Event; | ||
|
||
namespace ProjBobcat.DefaultComponent.Installer.ModPackInstaller; | ||
|
||
public class ModPackInstallerBase : InstallerBase | ||
{ | ||
protected readonly ConcurrentBag<DownloadFile> FailedFiles = new(); | ||
protected int TotalDownloaded, NeedToDownload; | ||
|
||
protected void WhenCompleted(object? sender, DownloadFileCompletedEventArgs e) | ||
{ | ||
if (sender is not DownloadFile file) return; | ||
|
||
TotalDownloaded++; | ||
|
||
var progress = (double)TotalDownloaded / NeedToDownload * 100; | ||
var retryStr = file.RetryCount > 0 ? $"[重试 - {file.RetryCount}] " : string.Empty; | ||
var fileName = file.FileName.Length > 20 | ||
? $"{file.FileName[..20]}..." | ||
: file.FileName; | ||
|
||
InvokeStatusChangedEvent($"{retryStr}下载整合包中的 Mods - {fileName} ({TotalDownloaded} / {NeedToDownload})", | ||
progress); | ||
|
||
if (!(e.Success ?? false)) FailedFiles.Add(file); | ||
} | ||
} |
138 changes: 138 additions & 0 deletions
138
ProjBobcat/ProjBobcat/DefaultComponent/Installer/ModPackInstaller/ModrinthInstaller.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Text.Json; | ||
using System.Threading.Tasks; | ||
using ProjBobcat.Class.Helper; | ||
using ProjBobcat.Class.Model; | ||
using ProjBobcat.Class.Model.Modrinth; | ||
using ProjBobcat.Interface; | ||
using SharpCompress.Archives; | ||
|
||
namespace ProjBobcat.DefaultComponent.Installer.ModPackInstaller; | ||
|
||
public sealed class ModrinthInstaller : ModPackInstallerBase, IModrinthInstaller | ||
{ | ||
public string GameId { get; set; } | ||
public string ModPackPath { get; set; } | ||
|
||
public async Task<ModrinthModPackIndexModel?> ReadIndexTask() | ||
{ | ||
using var archive = ArchiveFactory.Open(Path.GetFullPath(ModPackPath)); | ||
var manifestEntry = | ||
archive.Entries.FirstOrDefault(x => x.Key.Equals("modrinth.index.json", StringComparison.OrdinalIgnoreCase)); | ||
|
||
if (manifestEntry == default) | ||
return default; | ||
|
||
await using var stream = manifestEntry.OpenEntryStream(); | ||
|
||
var manifestModel = await JsonSerializer.DeserializeAsync(stream, ModrinthModPackIndexModelContext.Default.ModrinthModPackIndexModel); | ||
|
||
return manifestModel; | ||
} | ||
|
||
public void Install() | ||
{ | ||
InstallTaskAsync().Wait(); | ||
} | ||
|
||
public async Task InstallTaskAsync() | ||
{ | ||
InvokeStatusChangedEvent("开始安装", 0); | ||
|
||
var index = await ReadIndexTask(); | ||
|
||
if (index == default) | ||
throw new Exception("无法读取到 Modrinth 的 manifest 文件"); | ||
|
||
var idPath = Path.Combine(RootPath, GamePathHelper.GetGamePath(GameId)); | ||
var downloadPath = Path.Combine(Path.GetFullPath(idPath), "mods"); | ||
|
||
var di = new DirectoryInfo(downloadPath); | ||
|
||
if (!di.Exists) | ||
di.Create(); | ||
|
||
var downloadFiles = new List<DownloadFile>(); | ||
|
||
foreach (var file in index.Files) | ||
{ | ||
if (string.IsNullOrEmpty(file.Path)) continue; | ||
if(file.Downloads.Length == 0) continue; | ||
|
||
var fullPath = Path.Combine(idPath, file.Path); | ||
var downloadDir = Path.GetDirectoryName(fullPath); | ||
var fileName = Path.GetFileName(fullPath); | ||
var checkSum = file.Hashes.TryGetValue("sha1", out var sha1) ? sha1 : string.Empty; | ||
|
||
var df = new DownloadFile | ||
{ | ||
CheckSum = checkSum, | ||
DownloadPath = downloadDir, | ||
DownloadUri = file.Downloads.RandomSample(), | ||
FileName = fileName, | ||
FileSize = file.Size | ||
}; | ||
df.Completed += WhenCompleted; | ||
|
||
downloadFiles.Add(df); | ||
} | ||
|
||
TotalDownloaded = 0; | ||
NeedToDownload = downloadFiles.Count; | ||
await DownloadHelper.AdvancedDownloadListFile(downloadFiles, new DownloadSettings | ||
{ | ||
DownloadParts = 4, | ||
RetryCount = 10, | ||
Timeout = (int)TimeSpan.FromMinutes(1).TotalMilliseconds, | ||
CheckFile = true, | ||
HashType = HashType.SHA1 | ||
}); | ||
|
||
if (!FailedFiles.IsEmpty) | ||
throw new NullReferenceException("未能下载全部的 Mods"); | ||
|
||
using var archive = ArchiveFactory.Open(Path.GetFullPath(ModPackPath)); | ||
|
||
TotalDownloaded = 0; | ||
NeedToDownload = archive.Entries.Count(); | ||
|
||
const string decompressPrefix = "overrides"; | ||
|
||
foreach (var entry in archive.Entries) | ||
{ | ||
if (!entry.Key.StartsWith(decompressPrefix, StringComparison.OrdinalIgnoreCase)) continue; | ||
|
||
var subPath = entry.Key[(decompressPrefix.Length + 1)..]; | ||
if (string.IsNullOrEmpty(subPath)) continue; | ||
|
||
var path = Path.Combine(Path.GetFullPath(idPath), subPath); | ||
var dirPath = Path.GetDirectoryName(path); | ||
|
||
if (!Directory.Exists(dirPath)) | ||
Directory.CreateDirectory(dirPath); | ||
if (entry.IsDirectory) | ||
{ | ||
if (!Directory.Exists(path)) | ||
Directory.CreateDirectory(path); | ||
continue; | ||
} | ||
|
||
var subPathLength = subPath.Length; | ||
var subPathName = subPathLength > 35 | ||
? $"...{subPath[(subPathLength - 15)..]}" | ||
: subPath; | ||
|
||
InvokeStatusChangedEvent($"解压缩安装文件:{subPathName}", (double)TotalDownloaded / NeedToDownload * 100); | ||
|
||
await using var fs = File.OpenWrite(path); | ||
entry.WriteTo(fs); | ||
|
||
TotalDownloaded++; | ||
} | ||
|
||
InvokeStatusChangedEvent("安装完成", 100); | ||
} | ||
} |
Oops, something went wrong.