22using System . Collections . Generic ;
33using System . Diagnostics ;
44using System . IO ;
5- using UnityDataTools . Analyzer . SQLite ;
6- using UnityDataTools . FileSystem ;
5+ using UnityDataTools . Analyzer . SQLite . Handlers ;
6+ using UnityDataTools . Analyzer . SQLite . Parsers ;
7+ using UnityDataTools . Analyzer . SQLite . Parsers . Models ;
8+ using UnityDataTools . Analyzer . SQLite . Writers ;
79
810namespace UnityDataTools . Analyzer ;
911
1012public class AnalyzerTool
1113{
1214 bool m_Verbose = false ;
1315
16+ public List < ISQLiteFileParser > parsers = new List < ISQLiteFileParser > ( )
17+ {
18+ new AddressablesBuildLayoutParser ( ) ,
19+ new SerializedFileParser ( ) ,
20+ } ;
21+
1422 public int Analyze (
1523 string path ,
1624 string databaseName ,
@@ -21,11 +29,18 @@ public int Analyze(
2129 {
2230 m_Verbose = verbose ;
2331
24- using SQLiteWriter writer = new ( databaseName , skipReferences ) ;
32+ using SQLiteWriter writer = new ( databaseName ) ;
2533
2634 try
2735 {
2836 writer . Begin ( ) ;
37+ foreach ( var parser in parsers )
38+ {
39+ parser . Verbose = verbose ;
40+ parser . SkipReferences = skipReferences ;
41+ parser . Init ( writer . Connection ) ;
42+
43+ }
2944 }
3045 catch ( Exception e )
3146 {
@@ -47,31 +62,52 @@ public int Analyze(
4762 int i = 1 ;
4863 foreach ( var file in files )
4964 {
50- if ( ShouldIgnoreFile ( file ) )
65+ bool foundParser = false ;
66+ foreach ( var parser in parsers )
67+ {
68+ if ( parser . CanParse ( file ) )
69+ {
70+ foundParser = true ;
71+ try
72+ {
73+ parser . Parse ( file ) ;
74+ ReportProgress ( Path . GetRelativePath ( path , file ) , i , files . Length ) ;
75+ countSuccess ++ ;
76+ }
77+ catch ( Exception e )
78+ {
79+ EraseProgressLine ( ) ;
80+ Console . Error . WriteLine ( ) ;
81+ Console . Error . WriteLine ( $ "Error processing file: { file } ") ;
82+ Console . WriteLine ( $ "{ e . GetType ( ) } : { e . Message } ") ;
83+ if ( m_Verbose )
84+ Console . WriteLine ( e . StackTrace ) ;
85+ countFailures ++ ;
86+ }
87+ }
88+ }
89+ if ( ! foundParser )
5190 {
5291 if ( m_Verbose )
5392 {
5493 var relativePath = Path . GetRelativePath ( path , file ) ;
5594 Console . WriteLine ( ) ;
5695 Console . WriteLine ( $ "Ignoring { relativePath } ") ;
5796 }
97+
5898 countIgnored ++ ;
5999 }
60- else if ( ! ProcessFile ( file , path , writer , i , files . Length ) )
61- {
62- countFailures ++ ;
63- }
64- else
65- {
66- countSuccess ++ ;
67- }
68100 ++ i ;
69101 }
70102
71103 Console . WriteLine ( ) ;
72104 Console . WriteLine ( $ "Finalizing database. Successfully processed files: { countSuccess } , Failed files: { countFailures } , Ignored files: { countIgnored } ") ;
73105
74106 writer . End ( ) ;
107+ foreach ( var parser in parsers )
108+ {
109+ parser . Dispose ( ) ;
110+ }
75111
76112 timer . Stop ( ) ;
77113 Console . WriteLine ( ) ;
@@ -80,158 +116,6 @@ public int Analyze(
80116 return 0 ;
81117 }
82118
83- bool ShouldIgnoreFile ( string file )
84- {
85- // Unfortunately there is no standard extension for AssetBundles, and SerializedFiles often have no extension at all.
86- // Also there is also no distinctive signature at the start of a SerializedFile to immediately recognize it based on its first bytes.
87- // This makes it difficult to use the "--search-pattern" argument to only pick those files.
88-
89- // Hence to reduce noise in UnityDataTool output we filter out files that we have a high confidence are
90- // NOT SerializedFiles or Unity Archives.
91-
92- string fileName = Path . GetFileName ( file ) ;
93- string extension = Path . GetExtension ( file ) ;
94-
95- return IgnoredFileNames . Contains ( fileName ) || IgnoredExtensions . Contains ( extension ) ;
96- }
97-
98- // These lists are based on expected output files in Player, AssetBundle, Addressables and ECS builds.
99- // However this is by no means exhaustive.
100- private static readonly HashSet < string > IgnoredFileNames = new ( )
101- {
102- ".DS_Store" , "boot.config" , "archive_dependencies.bin" , "scene_info.bin" , "app.info" , "link.xml" ,
103- "catalog.bin" , "catalog.hash"
104- } ;
105-
106- private static readonly HashSet < string > IgnoredExtensions = new ( )
107- {
108- ".txt" , ".resS" , ".resource" , ".json" , ".dll" , ".pdb" , ".exe" , ".manifest" , ".entities" , ".entityheader" ,
109- ".ini" , ".config"
110- } ;
111-
112- bool ProcessFile ( string file , string rootDirectory , SQLiteWriter writer , int fileIndex , int cntFiles )
113- {
114- bool successful = true ;
115- try
116- {
117- if ( IsUnityArchive ( file ) )
118- {
119- using ( UnityArchive archive = UnityFileSystem . MountArchive ( file , "archive:" + Path . DirectorySeparatorChar ) )
120- {
121- if ( archive == null )
122- throw new FileLoadException ( $ "Failed to mount archive: { file } ") ;
123-
124- try
125- {
126- var assetBundleName = Path . GetRelativePath ( rootDirectory , file ) ;
127-
128- writer . BeginAssetBundle ( assetBundleName , new FileInfo ( file ) . Length ) ;
129- ReportProgress ( assetBundleName , fileIndex , cntFiles ) ;
130-
131- foreach ( var node in archive . Nodes )
132- {
133- if ( node . Flags . HasFlag ( ArchiveNodeFlags . SerializedFile ) )
134- {
135- try
136- {
137- writer . WriteSerializedFile ( node . Path , "archive:/" + node . Path , Path . GetDirectoryName ( file ) ) ;
138- }
139- catch ( Exception e )
140- {
141- // the most likely exception here is Microsoft.Data.Sqlite.SqliteException,
142- // for example 'UNIQUE constraint failed: serialized_files.id'.
143- // or 'UNIQUE constraint failed: objects.id' which can happen
144- // if AssetBundles from different builds are being processed by a single call to Analyze
145- // or if there is a Unity Data Tool bug.
146- EraseProgressLine ( ) ;
147- Console . Error . WriteLine ( $ "Error processing { node . Path } in archive { file } ") ;
148- Console . Error . WriteLine ( e . Message ) ;
149- Console . WriteLine ( ) ;
150-
151- // It is possible some files inside an archive will pass and others will fail, to have a partial analyze.
152- // Overall that is reported as a failure
153- successful = false ;
154- }
155- }
156- }
157- }
158- finally
159- {
160- writer . EndAssetBundle ( ) ;
161- }
162- }
163- }
164- else
165- {
166- // This isn't a Unity Archive file. Try to open it as a SerializedFile.
167- // Unfortunately there is no standard file extension, or clear signature at the start of the file,
168- // to test if it truly is a SerializedFile. So this will process files that are clearly not unity build files,
169- // and there is a chance for crashes and freezes if the parser misinterprets the file content.
170- var relativePath = Path . GetRelativePath ( rootDirectory , file ) ;
171- writer . WriteSerializedFile ( relativePath , file , Path . GetDirectoryName ( file ) ) ;
172-
173- ReportProgress ( relativePath , fileIndex , cntFiles ) ;
174- }
175-
176- EraseProgressLine ( ) ;
177- }
178- catch ( NotSupportedException )
179- {
180- EraseProgressLine ( ) ;
181- Console . Error . WriteLine ( ) ;
182- //A "failed to load" error will already be logged by the UnityFileSystem library
183-
184- successful = false ;
185- }
186- catch ( Exception e )
187- {
188- EraseProgressLine ( ) ;
189- Console . Error . WriteLine ( ) ;
190- Console . Error . WriteLine ( $ "Error processing file: { file } ") ;
191- Console . WriteLine ( $ "{ e . GetType ( ) } : { e . Message } ") ;
192- if ( m_Verbose )
193- Console . WriteLine ( e . StackTrace ) ;
194-
195- successful = false ;
196- }
197-
198- return successful ;
199- }
200-
201- private static bool IsUnityArchive ( string filePath )
202- {
203- // Check whether a file is a Unity Archive (AssetBundle) by looking for known signatures at the start of the file.
204- // "UnifyFS" is the current signature, but some older formats of the file are still supported
205- string [ ] signatures = { "UnityFS" , "UnityWeb" , "UnityRaw" , "UnityArchive" } ;
206- int maxLen = 12 ; // "UnityArchive".Length
207- byte [ ] buffer = new byte [ maxLen ] ;
208-
209- using ( var fs = new FileStream ( filePath , FileMode . Open , FileAccess . Read , FileShare . Read ) )
210- {
211- int read = fs . Read ( buffer , 0 , buffer . Length ) ;
212- foreach ( var sig in signatures )
213- {
214- if ( read >= sig . Length )
215- {
216- bool match = true ;
217- for ( int i = 0 ; i < sig . Length ; ++ i )
218- {
219- if ( buffer [ i ] != sig [ i ] )
220- {
221- match = false ;
222- break ;
223- }
224- }
225- if ( match )
226- return true ;
227- }
228- }
229- return false ;
230- }
231- }
232-
233-
234-
235119 int m_LastProgressMessageLength = 0 ;
236120
237121 void ReportProgress ( string relativePath , int fileIndex , int cntFiles )
0 commit comments