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