Skip to content

Commit c5b95c3

Browse files
committed
add helpers for game resources resolve
now ProjBobcat can resolve following resources: - Game Mods - Game Resource Packs - Game Shader Packs See GameResourcesResolveHelper for more information
1 parent 74d87fd commit c5b95c3

File tree

11 files changed

+2876
-1
lines changed

11 files changed

+2876
-1
lines changed

ProjBobcat/ProjBobcat/Class/Helper/DirectoryHelper.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using System.IO;
1+
using System.Collections.Generic;
2+
using System.IO;
3+
using System.Linq;
24

35
namespace ProjBobcat.Class.Helper;
46

@@ -25,4 +27,17 @@ public static void CleanDirectory(string path, bool deleteDirectory = false)
2527
foreach (var subDirectory in directory.GetDirectories()) subDirectory.Delete(true);
2628
}
2729
}
30+
31+
/// <summary>
32+
/// 获取一个目录下所有文件和文件夹
33+
/// </summary>
34+
/// <param name="path">需要获取的路径</param>
35+
/// <returns>(路径,是否是文件夹)</returns>
36+
public static IEnumerable<(string, bool)> EnumerateFilesAndDirectories(string path)
37+
{
38+
var files = Directory.EnumerateFiles(path).Select(p => (p, false));
39+
var dirs = Directory.EnumerateDirectories(path).Select(p => (p, true));
40+
41+
return files.Concat(dirs);
42+
}
2843
}
Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
using Newtonsoft.Json;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Runtime.CompilerServices;
6+
using System.Threading.Tasks;
7+
using System.Threading;
8+
using System;
9+
using System.Collections.Immutable;
10+
using ProjBobcat.Class.Model.GameResource;
11+
using ProjBobcat.Class.Model.GameResource.ResolvedInfo;
12+
using Newtonsoft.Json.Linq;
13+
using ProjBobcat.Class.Helper.TOMLParser;
14+
using ProjBobcat.Class.Model.Fabric;
15+
using SharpCompress.Archives;
16+
17+
namespace ProjBobcat.Class.Helper;
18+
19+
public static class GameResourcesResolveHelper
20+
{
21+
public static async IAsyncEnumerable<GameModResolvedInfo> ResolveModListAsync(IEnumerable<string> files, [EnumeratorCancellation] CancellationToken ct)
22+
{
23+
foreach (var file in files)
24+
{
25+
if (ct.IsCancellationRequested) yield break;
26+
27+
var ext = Path.GetExtension(file);
28+
if (string.IsNullOrEmpty(ext) ||
29+
!(ext.Equals(".jar", StringComparison.OrdinalIgnoreCase) ||
30+
ext.Equals(".disabled", StringComparison.OrdinalIgnoreCase)))
31+
continue;
32+
33+
if (!ArchiveHelper.TryOpen(file, out var archive)) continue;
34+
if (archive == null) continue;
35+
36+
var modInfoEntry =
37+
archive.Entries.FirstOrDefault(e =>
38+
e.Key.Equals("mcmod.info", StringComparison.OrdinalIgnoreCase));
39+
var fabricModInfoEntry =
40+
archive.Entries.FirstOrDefault(e =>
41+
e.Key.Equals("fabric.mod.json", StringComparison.OrdinalIgnoreCase));
42+
var tomlInfoEntry =
43+
archive.Entries.FirstOrDefault(e =>
44+
e.Key.Equals("META-INF/mods.toml", StringComparison.OrdinalIgnoreCase));
45+
46+
var isEnabled = ext.Equals(".jar", StringComparison.OrdinalIgnoreCase);
47+
48+
async Task<GameModResolvedInfo?> GetLegacyModInfo(IArchiveEntry entry)
49+
{
50+
await using var stream = entry.OpenEntryStream();
51+
using var sR = new StreamReader(stream);
52+
using var parser = new TOMLParser.TOMLParser(sR);
53+
var pResult = parser.TryParse(out var table, out _);
54+
55+
if (!pResult) return null;
56+
if (!table.HasKey("mods")) return null;
57+
58+
var innerTable = table["mods"];
59+
60+
if (innerTable is not TomlArray arr) return null;
61+
if (arr.ChildrenCount == 0) return null;
62+
63+
var infoTable = arr.Children.First();
64+
65+
var title = infoTable.HasKey("modId")
66+
? infoTable["modId"]?.AsString
67+
: Path.GetFileName(file);
68+
var author = infoTable.HasKey("authors")
69+
? infoTable["authors"]?.AsString
70+
: null;
71+
var version = infoTable.HasKey("version")
72+
? infoTable["version"]?.AsString
73+
: null;
74+
75+
return new GameModResolvedInfo(author?.Value, file, null, title, version?.Value, "Forge", isEnabled);
76+
}
77+
78+
async Task<GameModResolvedInfo> GetNewModInfo(IArchiveEntry entry)
79+
{
80+
await using var stream = entry.OpenEntryStream();
81+
using var sR = new StreamReader(stream);
82+
var content = await sR.ReadToEndAsync();
83+
var tempModel = JsonConvert.DeserializeObject<object>(content);
84+
85+
var model = new List<GameModInfoModel>();
86+
switch (tempModel)
87+
{
88+
case JObject jObj:
89+
var obj = jObj.ToObject<GameModInfoModel>();
90+
91+
if (obj == null) break;
92+
93+
model.Add(obj);
94+
break;
95+
case JArray jArr:
96+
model = jArr.ToObject<List<GameModInfoModel>>() ?? new List<GameModInfoModel>();
97+
break;
98+
}
99+
100+
var authors = new HashSet<string>();
101+
foreach (var author in model.Where(m => m.AuthorList != null).SelectMany(m => m.AuthorList!))
102+
authors.Add(author);
103+
104+
var baseMod = model.FirstOrDefault(m => string.IsNullOrEmpty(m.Parent));
105+
106+
if (baseMod == null)
107+
{
108+
baseMod = model.First();
109+
model.RemoveAt(0);
110+
}
111+
else
112+
{
113+
model.Remove(baseMod);
114+
}
115+
116+
var authorStr = string.Join(',', authors);
117+
var authorResult = string.IsNullOrEmpty(authorStr) ? null : authorStr;
118+
var modList = model.Where(m => !string.IsNullOrEmpty(m.Name)).Select(m => m.Name!).ToImmutableList();
119+
var titleResult = string.IsNullOrEmpty(baseMod.Name) ? Path.GetFileName(file) : baseMod.Name;
120+
121+
var displayModel = new GameModResolvedInfo(authorResult, file, modList, titleResult, baseMod.Version,
122+
"Forge *", isEnabled);
123+
124+
return displayModel;
125+
}
126+
127+
async Task<GameModResolvedInfo> GetFabricModInfo(IArchiveEntry entry)
128+
{
129+
await using var stream = entry.OpenEntryStream();
130+
using var sR = new StreamReader(stream);
131+
var content = await sR.ReadToEndAsync();
132+
var tempModel = JsonConvert.DeserializeObject<FabricModInfoModel>(content);
133+
134+
var author = tempModel?.Authors?.Any() ?? false
135+
? string.Join(',', tempModel.Authors)
136+
: null;
137+
var modList = tempModel?.Depends?.Select(d => d.Key)?.ToImmutableList();
138+
var titleResult = string.IsNullOrEmpty(tempModel?.Id) ? Path.GetFileName(file) : tempModel.Id;
139+
var versionResult = string.IsNullOrEmpty(tempModel?.Version) ? null : tempModel.Version;
140+
141+
return new GameModResolvedInfo(author, file, modList, titleResult, versionResult, "Fabric", isEnabled);
142+
}
143+
144+
GameModResolvedInfo? result = null;
145+
146+
if (modInfoEntry != null)
147+
{
148+
result = await GetNewModInfo(modInfoEntry);
149+
goto ReturnResult;
150+
}
151+
152+
if (tomlInfoEntry != null)
153+
{
154+
var info = await GetLegacyModInfo(tomlInfoEntry);
155+
156+
if (info == null) continue;
157+
158+
result = info;
159+
160+
goto ReturnResult;
161+
}
162+
163+
if (fabricModInfoEntry != null)
164+
{
165+
result = await GetFabricModInfo(fabricModInfoEntry);
166+
}
167+
168+
ReturnResult:
169+
if (result != null)
170+
yield return result;
171+
}
172+
}
173+
174+
public static async IAsyncEnumerable<GameResourcePackResolvedInfo> ResolveResourcePackAsync(IEnumerable<(string, bool)> files,
175+
[EnumeratorCancellation] CancellationToken ct)
176+
{
177+
async Task<GameResourcePackResolvedInfo?> ResolveResPackFile(string file)
178+
{
179+
var ext = Path.GetExtension(file);
180+
181+
if (!ext.Equals(".zip", StringComparison.OrdinalIgnoreCase)) return null;
182+
if (!ArchiveHelper.TryOpen(file, out var archive)) return null;
183+
if (archive == null) return null;
184+
185+
var packIconEntry =
186+
archive.Entries.FirstOrDefault(e => e.Key.Equals("pack.png", StringComparison.OrdinalIgnoreCase));
187+
var packInfoEntry = archive.Entries.FirstOrDefault(e =>
188+
e.Key.Equals("pack.mcmeta", StringComparison.OrdinalIgnoreCase));
189+
190+
var fileName = Path.GetFileName(file);
191+
byte[]? imageBytes;
192+
string? description = null;
193+
var version = -1;
194+
195+
if (packIconEntry != null)
196+
{
197+
await using var stream = packIconEntry.OpenEntryStream();
198+
await using var ms = new MemoryStream();
199+
await stream.CopyToAsync(ms, ct);
200+
201+
imageBytes = ms.ToArray();
202+
}
203+
else
204+
{
205+
return null;
206+
}
207+
208+
if (packInfoEntry != null)
209+
{
210+
await using var stream = packInfoEntry.OpenEntryStream();
211+
using var sR = new StreamReader(stream);
212+
var content = await sR.ReadToEndAsync();
213+
var model = JsonConvert.DeserializeObject<GameResourcePackModel>(content);
214+
215+
description = model?.Pack?.Description;
216+
version = model?.Pack?.PackFormat ?? -1;
217+
}
218+
219+
return new GameResourcePackResolvedInfo(fileName, description, version, imageBytes);
220+
}
221+
222+
async Task<GameResourcePackResolvedInfo?> ResolveResPackDir(string dir)
223+
{
224+
var iconPath = Path.Combine(dir, "pack.png");
225+
var infoPath = Path.Combine(dir, "pack.mcmeta");
226+
227+
if (!File.Exists(iconPath)) return null;
228+
229+
var fileName = dir.Split('\\').Last();
230+
var imageBytes = await File.ReadAllBytesAsync(iconPath, ct);
231+
string? description = null;
232+
var version = -1;
233+
234+
if (File.Exists(infoPath))
235+
{
236+
var content = await File.ReadAllTextAsync(infoPath, ct);
237+
var model = JsonConvert.DeserializeObject<GameResourcePackModel>(content);
238+
239+
description = model?.Pack?.Description;
240+
version = model?.Pack?.PackFormat ?? -1;
241+
}
242+
243+
return new GameResourcePackResolvedInfo(fileName, description, version, imageBytes);
244+
}
245+
246+
foreach (var (path, isDir) in files)
247+
{
248+
if (ct.IsCancellationRequested) yield break;
249+
250+
if (!isDir)
251+
{
252+
var result = await ResolveResPackFile(path);
253+
254+
if (result == null) continue;
255+
256+
yield return result;
257+
}
258+
else
259+
{
260+
var result = await ResolveResPackDir(path);
261+
262+
if (result == null) continue;
263+
264+
yield return result;
265+
}
266+
}
267+
}
268+
269+
public static IEnumerable<GameShaderPackResolvedInfo> ResolveShaderPack(IEnumerable<(string, bool)> paths, CancellationToken ct)
270+
{
271+
GameShaderPackResolvedInfo? ResolveShaderPackFile(string file)
272+
{
273+
if (!ArchiveHelper.TryOpen(file, out var archive)) return null;
274+
if (archive == null) return null;
275+
if (!archive.Entries.Any(e => e.Key.StartsWith("shaders/", StringComparison.OrdinalIgnoreCase)))
276+
return null;
277+
278+
var model = new GameShaderPackResolvedInfo(Path.GetFileName(file), false);
279+
280+
return model;
281+
}
282+
283+
GameShaderPackResolvedInfo? ResolveShaderPackDir(string dir)
284+
{
285+
var shaderPath = Path.Combine(dir, "shaders");
286+
287+
if (!Directory.Exists(shaderPath)) return null;
288+
289+
return new GameShaderPackResolvedInfo(dir.Split('\\').Last(), true);
290+
}
291+
292+
foreach (var (path, isDir) in paths)
293+
{
294+
if (ct.IsCancellationRequested) yield break;
295+
296+
if (!isDir)
297+
{
298+
var result = ResolveShaderPackFile(path);
299+
300+
if (result == null) continue;
301+
302+
yield return result;
303+
}
304+
else
305+
{
306+
var result = ResolveShaderPackDir(path);
307+
308+
if (result == null) continue;
309+
310+
yield return result;
311+
}
312+
}
313+
}
314+
}

0 commit comments

Comments
 (0)