Skip to content

Commit

Permalink
add helpers for game resources resolve
Browse files Browse the repository at this point in the history
now ProjBobcat can resolve following resources:

- Game Mods
- Game Resource Packs
- Game Shader Packs

See GameResourcesResolveHelper for more information
  • Loading branch information
laolarou726 committed Jan 18, 2023
1 parent 74d87fd commit c5b95c3
Show file tree
Hide file tree
Showing 11 changed files with 2,876 additions and 1 deletion.
17 changes: 16 additions & 1 deletion ProjBobcat/ProjBobcat/Class/Helper/DirectoryHelper.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.IO;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace ProjBobcat.Class.Helper;

Expand All @@ -25,4 +27,17 @@ public static void CleanDirectory(string path, bool deleteDirectory = false)
foreach (var subDirectory in directory.GetDirectories()) subDirectory.Delete(true);
}
}

/// <summary>
/// 获取一个目录下所有文件和文件夹
/// </summary>
/// <param name="path">需要获取的路径</param>
/// <returns>(路径,是否是文件夹)</returns>
public static IEnumerable<(string, bool)> EnumerateFilesAndDirectories(string path)
{
var files = Directory.EnumerateFiles(path).Select(p => (p, false));
var dirs = Directory.EnumerateDirectories(path).Select(p => (p, true));

return files.Concat(dirs);
}
}
314 changes: 314 additions & 0 deletions ProjBobcat/ProjBobcat/Class/Helper/GameResourcesResolveHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,314 @@
using Newtonsoft.Json;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Threading;
using System;
using System.Collections.Immutable;
using ProjBobcat.Class.Model.GameResource;
using ProjBobcat.Class.Model.GameResource.ResolvedInfo;
using Newtonsoft.Json.Linq;
using ProjBobcat.Class.Helper.TOMLParser;
using ProjBobcat.Class.Model.Fabric;
using SharpCompress.Archives;

namespace ProjBobcat.Class.Helper;

public static class GameResourcesResolveHelper
{
public static async IAsyncEnumerable<GameModResolvedInfo> ResolveModListAsync(IEnumerable<string> files, [EnumeratorCancellation] CancellationToken ct)
{
foreach (var file in files)
{
if (ct.IsCancellationRequested) yield break;

var ext = Path.GetExtension(file);
if (string.IsNullOrEmpty(ext) ||
!(ext.Equals(".jar", StringComparison.OrdinalIgnoreCase) ||
ext.Equals(".disabled", StringComparison.OrdinalIgnoreCase)))
continue;

if (!ArchiveHelper.TryOpen(file, out var archive)) continue;
if (archive == null) continue;

var modInfoEntry =
archive.Entries.FirstOrDefault(e =>
e.Key.Equals("mcmod.info", StringComparison.OrdinalIgnoreCase));
var fabricModInfoEntry =
archive.Entries.FirstOrDefault(e =>
e.Key.Equals("fabric.mod.json", StringComparison.OrdinalIgnoreCase));
var tomlInfoEntry =
archive.Entries.FirstOrDefault(e =>
e.Key.Equals("META-INF/mods.toml", StringComparison.OrdinalIgnoreCase));

var isEnabled = ext.Equals(".jar", StringComparison.OrdinalIgnoreCase);

async Task<GameModResolvedInfo?> GetLegacyModInfo(IArchiveEntry entry)
{
await using var stream = entry.OpenEntryStream();
using var sR = new StreamReader(stream);
using var parser = new TOMLParser.TOMLParser(sR);
var pResult = parser.TryParse(out var table, out _);

if (!pResult) return null;
if (!table.HasKey("mods")) return null;

var innerTable = table["mods"];

if (innerTable is not TomlArray arr) return null;
if (arr.ChildrenCount == 0) return null;

var infoTable = arr.Children.First();

var title = infoTable.HasKey("modId")
? infoTable["modId"]?.AsString
: Path.GetFileName(file);
var author = infoTable.HasKey("authors")
? infoTable["authors"]?.AsString
: null;
var version = infoTable.HasKey("version")
? infoTable["version"]?.AsString
: null;

return new GameModResolvedInfo(author?.Value, file, null, title, version?.Value, "Forge", isEnabled);
}

async Task<GameModResolvedInfo> GetNewModInfo(IArchiveEntry entry)
{
await using var stream = entry.OpenEntryStream();
using var sR = new StreamReader(stream);
var content = await sR.ReadToEndAsync();
var tempModel = JsonConvert.DeserializeObject<object>(content);

var model = new List<GameModInfoModel>();
switch (tempModel)
{
case JObject jObj:
var obj = jObj.ToObject<GameModInfoModel>();

if (obj == null) break;

model.Add(obj);
break;
case JArray jArr:
model = jArr.ToObject<List<GameModInfoModel>>() ?? new List<GameModInfoModel>();
break;
}

var authors = new HashSet<string>();
foreach (var author in model.Where(m => m.AuthorList != null).SelectMany(m => m.AuthorList!))
authors.Add(author);

var baseMod = model.FirstOrDefault(m => string.IsNullOrEmpty(m.Parent));

if (baseMod == null)
{
baseMod = model.First();
model.RemoveAt(0);
}
else
{
model.Remove(baseMod);
}

var authorStr = string.Join(',', authors);
var authorResult = string.IsNullOrEmpty(authorStr) ? null : authorStr;
var modList = model.Where(m => !string.IsNullOrEmpty(m.Name)).Select(m => m.Name!).ToImmutableList();
var titleResult = string.IsNullOrEmpty(baseMod.Name) ? Path.GetFileName(file) : baseMod.Name;

var displayModel = new GameModResolvedInfo(authorResult, file, modList, titleResult, baseMod.Version,
"Forge *", isEnabled);

return displayModel;
}

async Task<GameModResolvedInfo> GetFabricModInfo(IArchiveEntry entry)
{
await using var stream = entry.OpenEntryStream();
using var sR = new StreamReader(stream);
var content = await sR.ReadToEndAsync();
var tempModel = JsonConvert.DeserializeObject<FabricModInfoModel>(content);

var author = tempModel?.Authors?.Any() ?? false
? string.Join(',', tempModel.Authors)
: null;
var modList = tempModel?.Depends?.Select(d => d.Key)?.ToImmutableList();
var titleResult = string.IsNullOrEmpty(tempModel?.Id) ? Path.GetFileName(file) : tempModel.Id;
var versionResult = string.IsNullOrEmpty(tempModel?.Version) ? null : tempModel.Version;

return new GameModResolvedInfo(author, file, modList, titleResult, versionResult, "Fabric", isEnabled);
}

GameModResolvedInfo? result = null;

if (modInfoEntry != null)
{
result = await GetNewModInfo(modInfoEntry);
goto ReturnResult;
}

if (tomlInfoEntry != null)
{
var info = await GetLegacyModInfo(tomlInfoEntry);

if (info == null) continue;

result = info;

goto ReturnResult;
}

if (fabricModInfoEntry != null)
{
result = await GetFabricModInfo(fabricModInfoEntry);
}

ReturnResult:
if (result != null)
yield return result;
}
}

public static async IAsyncEnumerable<GameResourcePackResolvedInfo> ResolveResourcePackAsync(IEnumerable<(string, bool)> files,
[EnumeratorCancellation] CancellationToken ct)
{
async Task<GameResourcePackResolvedInfo?> ResolveResPackFile(string file)
{
var ext = Path.GetExtension(file);

if (!ext.Equals(".zip", StringComparison.OrdinalIgnoreCase)) return null;
if (!ArchiveHelper.TryOpen(file, out var archive)) return null;
if (archive == null) return null;

var packIconEntry =
archive.Entries.FirstOrDefault(e => e.Key.Equals("pack.png", StringComparison.OrdinalIgnoreCase));
var packInfoEntry = archive.Entries.FirstOrDefault(e =>
e.Key.Equals("pack.mcmeta", StringComparison.OrdinalIgnoreCase));

var fileName = Path.GetFileName(file);
byte[]? imageBytes;
string? description = null;
var version = -1;

if (packIconEntry != null)
{
await using var stream = packIconEntry.OpenEntryStream();
await using var ms = new MemoryStream();
await stream.CopyToAsync(ms, ct);

imageBytes = ms.ToArray();
}
else
{
return null;
}

if (packInfoEntry != null)
{
await using var stream = packInfoEntry.OpenEntryStream();
using var sR = new StreamReader(stream);
var content = await sR.ReadToEndAsync();
var model = JsonConvert.DeserializeObject<GameResourcePackModel>(content);

description = model?.Pack?.Description;
version = model?.Pack?.PackFormat ?? -1;
}

return new GameResourcePackResolvedInfo(fileName, description, version, imageBytes);
}

async Task<GameResourcePackResolvedInfo?> ResolveResPackDir(string dir)
{
var iconPath = Path.Combine(dir, "pack.png");
var infoPath = Path.Combine(dir, "pack.mcmeta");

if (!File.Exists(iconPath)) return null;

var fileName = dir.Split('\\').Last();
var imageBytes = await File.ReadAllBytesAsync(iconPath, ct);
string? description = null;
var version = -1;

if (File.Exists(infoPath))
{
var content = await File.ReadAllTextAsync(infoPath, ct);
var model = JsonConvert.DeserializeObject<GameResourcePackModel>(content);

description = model?.Pack?.Description;
version = model?.Pack?.PackFormat ?? -1;
}

return new GameResourcePackResolvedInfo(fileName, description, version, imageBytes);
}

foreach (var (path, isDir) in files)
{
if (ct.IsCancellationRequested) yield break;

if (!isDir)
{
var result = await ResolveResPackFile(path);

if (result == null) continue;

yield return result;
}
else
{
var result = await ResolveResPackDir(path);

if (result == null) continue;

yield return result;
}
}
}

public static IEnumerable<GameShaderPackResolvedInfo> ResolveShaderPack(IEnumerable<(string, bool)> paths, CancellationToken ct)
{
GameShaderPackResolvedInfo? ResolveShaderPackFile(string file)
{
if (!ArchiveHelper.TryOpen(file, out var archive)) return null;
if (archive == null) return null;
if (!archive.Entries.Any(e => e.Key.StartsWith("shaders/", StringComparison.OrdinalIgnoreCase)))
return null;

var model = new GameShaderPackResolvedInfo(Path.GetFileName(file), false);

return model;
}

GameShaderPackResolvedInfo? ResolveShaderPackDir(string dir)
{
var shaderPath = Path.Combine(dir, "shaders");

if (!Directory.Exists(shaderPath)) return null;

return new GameShaderPackResolvedInfo(dir.Split('\\').Last(), true);
}

foreach (var (path, isDir) in paths)
{
if (ct.IsCancellationRequested) yield break;

if (!isDir)
{
var result = ResolveShaderPackFile(path);

if (result == null) continue;

yield return result;
}
else
{
var result = ResolveShaderPackDir(path);

if (result == null) continue;

yield return result;
}
}
}
}
Loading

0 comments on commit c5b95c3

Please sign in to comment.