1+ using  CS2 . Util . ModVerifier . Models ; 
2+ using  Spectre . Console ; 
3+ using  System . Security . Cryptography ; 
4+ using  System . Text ; 
5+ using  System . Text . Json ; 
6+ using  nietras . SeparatedValues ; 
7+ 
8+ namespace  CS2 . Util . ModVerifier 
9+ { 
10+     internal  class  Program 
11+     { 
12+         static async  Task  Main ( string [ ]  args ) 
13+         { 
14+             Console . OutputEncoding  =  Encoding . UTF8 ; 
15+             AnsiConsole . Write ( new  FigletText ( "Mod Verifier" ) . Color ( Color . LightCyan1 ) ) ; 
16+             const  string  modsPath  = 
17+                 @"%AppData%\..\LocalLow\Colossal Order\Cities Skylines II\.cache\Mods\mods_subscribed" ; 
18+             var  actualPath  =  Path . GetFullPath ( Environment . ExpandEnvironmentVariables ( modsPath ) ) ; 
19+             var  displayPath  =  new  TextPath ( actualPath ) 
20+                 . RootColor ( Color . Red ) 
21+                 . SeparatorColor ( Color . Green ) 
22+                 . StemColor ( Color . Blue ) 
23+                 . LeafColor ( Color . Yellow ) ; 
24+             AnsiConsole . MarkupLine ( "[green]Current detected mods path[/]:" ) ; 
25+             AnsiConsole . Write ( displayPath ) ; 
26+             AnsiConsole . WriteLine ( ) ; 
27+             var  scanMods  =  AnsiConsole . Prompt ( 
28+                 new  TextPrompt < bool > ( "Scan mods?" ) 
29+                     . AddChoice ( true ) 
30+                     . AddChoice ( false ) 
31+                     . DefaultValue ( true ) 
32+                     . WithConverter ( choice =>  choice  ?  "y"  :  "n" ) ) ; 
33+             if  ( ! scanMods ) 
34+             { 
35+                 return ; 
36+             } 
37+ 
38+             var  table  =  new  Table ( ) . Centered ( ) ; 
39+             var  modInfos  =  new  List < ModInfo > ( ) ; 
40+             AnsiConsole . Live ( table ) 
41+                 . AutoClear ( false ) 
42+                 . Start ( ctx => 
43+                 { 
44+                     table . Border  =  TableBorder . MinimalHeavyHead ; 
45+                     table . AddColumn ( "[green]Name[/]" ) ; 
46+                     table . AddColumn ( "[yellow]Id[/]" ) ; 
47+                     table . AddColumn ( "[cyan]Author[/]" ) ; 
48+                     table . Expand ( ) ; 
49+                     ctx . Refresh ( ) ; 
50+                     var  mods  =  Directory . GetDirectories ( actualPath ) 
51+                         . Where ( x =>  File . Exists ( Path . Combine ( x ,  ".metadata" ,  "metadata.json" ) ) ) ; 
52+                     foreach  ( var  mod  in  mods ) 
53+                     { 
54+                         var  metadata  =  JsonSerializer . Deserialize ( 
55+                             File . ReadAllText ( Path . Combine ( mod ,  ".metadata" ,  "metadata.json" ) ) , 
56+                             ModInfoContext . Default . ModInfo ) ; 
57+                         if  ( metadata  is  null ) 
58+                         { 
59+                             ctx . Refresh ( ) ; 
60+                             Thread . Sleep ( 70 ) ; 
61+                             continue ; 
62+                         } 
63+ 
64+                         modInfos . Add ( metadata ) ; 
65+                         table . AddRow ( [ 
66+                             $ "[green]{ metadata . DisplayName . EscapeMarkup ( ) } [/]",  $ "[yellow]{ metadata . Id . ToString ( ) } [/]", 
67+                             $ "[cyan]{ metadata . Author . EscapeMarkup ( ) } [/]"
68+                         ] ) ; 
69+                         ctx . Refresh ( ) ; 
70+                         Thread . Sleep ( 20 ) ; 
71+                     } 
72+                 } ) ; 
73+             var  verifyMods  =  AnsiConsole . Prompt ( 
74+                 new  TextPrompt < bool > ( "Verify mods?" ) 
75+                     . AddChoice ( true ) 
76+                     . AddChoice ( false ) 
77+                     . DefaultValue ( true ) 
78+                     . WithConverter ( choice =>  choice  ?  "y"  :  "n" ) ) ; 
79+             if  ( ! verifyMods ) 
80+             { 
81+                 return ; 
82+             } 
83+ 
84+             var  o  =  new  object ( ) ; 
85+             var  verified  =  new  List < ( ModInfo ,  bool ) > ( ) ; 
86+             await  AnsiConsole . Status ( ) 
87+                 . StartAsync ( "Verifying..." ,  async  ctx => 
88+                 { 
89+                     await  Parallel . ForEachAsync ( modInfos ,  async  ( mod ,  token )  => 
90+                         { 
91+                             var  path  =  mod . LocalData . FolderAbsolutePath ; 
92+                             var  name  =  mod . Name ; 
93+                             var  manifestVersion  = 
94+                                 await  File . ReadAllTextAsync ( Path . Combine ( path ,  ".cpatch" ,  name ,  "version" ) ,  token ) ; 
95+                             var  manifests  =  new  List < string > ( ) ; 
96+                             await  using  var  fs  =  File . OpenRead ( Path . Combine ( Path . Combine ( path ,  ".cpatch" ,  name , 
97+                                 manifestVersion , 
98+                                 "complete" ,  "manifest" ) ) ) ; 
99+                             using  var  sr  =  new  StreamReader ( fs ) ; 
100+                             // Name 
101+                             _  =  await  sr . ReadLineAsync ( token ) ; 
102+                             // Hash method, SHA256 
103+                             _  =  await  sr . ReadLineAsync ( token ) ; 
104+                             // Empty 
105+                             _  =  await  sr . ReadLineAsync ( token ) ; 
106+                             var  csvReader  =  await  Sep . New ( ',' ) . Reader ( opt =>  opt  with 
107+                             { 
108+                                 HasHeader  =  false , 
109+                                 DisableColCountCheck  =  true , 
110+                                 Unescape  =  true 
111+                             } ) . FromAsync ( sr ,  cancellationToken :  token ) ; 
112+                             await  foreach  ( var  results  in  csvReader ) 
113+                             { 
114+                                 var  fullPath  =  Path . Combine ( path ,  results [ 0 ] . ToString ( ) ) ; 
115+                                 var  file  =  new  FileInfo ( fullPath ) ; 
116+                                 if  ( ! file . Exists ) 
117+                                 { 
118+                                     lock  ( o ) 
119+                                     { 
120+                                         AnsiConsole . MarkupLine ( 
121+                                             $ "[red][[ERR]][/] { mod . DisplayName . EscapeMarkup ( ) }  is broken!") ; 
122+                                         verified . Add ( ( mod ,  false ) ) ; 
123+                                     } 
124+ 
125+                                     return ; 
126+                                 } 
127+ 
128+                                 if  ( file . Length . ToString ( )  !=  results [ 1 ] . ToString ( ) ) 
129+                                 { 
130+                                     lock  ( o ) 
131+                                     { 
132+                                         AnsiConsole . MarkupLine ( 
133+                                             $ "[red][[ERR]][/] { mod . DisplayName . EscapeMarkup ( ) }  is broken!") ; 
134+                                         verified . Add ( ( mod ,  false ) ) ; 
135+                                     } 
136+ 
137+                                     return ; 
138+                                 } 
139+ 
140+                                 var  hash  =  SHA256 . HashData ( file . OpenRead ( ) ) ; 
141+                                 if  ( Convert . ToBase64String ( hash ) . Replace ( "/" ,  "_" ) . Replace ( "+" ,  "-" )  == 
142+                                     results [ 2 ] . ToString ( ) ) 
143+                                     continue ; 
144+                                 lock  ( o ) 
145+                                 { 
146+                                     AnsiConsole . MarkupLine ( 
147+                                         $ "[red][[ERR]][/] { mod . DisplayName . EscapeMarkup ( ) }  is broken!") ; 
148+                                     verified . Add ( ( mod ,  false ) ) ; 
149+                                 } 
150+ 
151+                                 return ; 
152+                             } 
153+ 
154+                             lock  ( o ) 
155+                             { 
156+                                 verified . Add ( ( mod ,  true ) ) ; 
157+                             } 
158+                         } 
159+                     ) ; 
160+                 } ) ; 
161+ 
162+             var  verifiedTable  =  new  Table ( ) . Centered ( ) ; 
163+             AnsiConsole . Live ( verifiedTable ) 
164+                 . AutoClear ( false ) 
165+                 . Start ( ctx => 
166+                 { 
167+                     verifiedTable . Border  =  TableBorder . MinimalHeavyHead ; 
168+                     verifiedTable . AddColumn ( "Name" ) ; 
169+                     verifiedTable . AddColumn ( "Id" ) ; 
170+                     verifiedTable . AddColumn ( "Author" ) ; 
171+                     verifiedTable . AddColumn ( "Verified" ) ; 
172+                     verifiedTable . Expand ( ) ; 
173+                     ctx . Refresh ( ) ; 
174+ 
175+                     foreach  ( var  ( mod ,  status )  in  verified ) 
176+                     { 
177+                         var  color  =  status  ?  "green"  :  "red" ; 
178+                         verifiedTable . AddRow ( [ 
179+                             $ "[{ color } ]{ mod . DisplayName . EscapeMarkup ( ) } [/]", 
180+                             $ "[{ color } ]{ mod . Id . ToString ( ) . EscapeMarkup ( ) } [/]", 
181+                             $ "[{ color } ]{ mod . Author . EscapeMarkup ( ) } [/]", 
182+                             $ "[{ color } ]{ status . ToString ( ) . EscapeMarkup ( ) } [/]"
183+                         ] ) ; 
184+                         ctx . Refresh ( ) ; 
185+                         Thread . Sleep ( 20 ) ; 
186+                     } 
187+                 } ) ; 
188+ 
189+             AnsiConsole . Write ( new  BreakdownChart ( ) 
190+                 . FullSize ( ) 
191+                 . AddItem ( "Correct" ,  verified . Count ( x =>  x . Item2 ) ,  Color . Green ) 
192+                 . AddItem ( "Broken" ,  verified . Count ( x =>  ! x . Item2 ) ,  Color . Red ) ) ; 
193+             AnsiConsole . MarkupLine ( "[cyan]press any key to exit...[/]" ) ; 
194+             Console . ReadKey ( ) ; 
195+         } 
196+     } 
197+ } 
0 commit comments