@@ -65,64 +65,76 @@ bool ShouldIgnoreFile(string file)
6565
6666 private static readonly HashSet < string > IgnoredExtensions = new ( )
6767 {
68- ".txt" , ".resS" , ".resource" , ".json" , ".dll" , ".pdb" , ".exe" , ".manifest" , ".entities" , ".entityheader"
68+ ".txt" , ".resS" , ".resource" , ".json" , ".dll" , ".pdb" , ".exe" , ".manifest" , ".entities" , ".entityheader" ,
69+ ".ini" , ".config"
6970 } ;
7071
71-
72- void ProcessFile ( string file , string rootDirectory )
72+ bool ProcessFile ( string file , string rootDirectory )
7373 {
74+ bool successful = true ;
7475 try
7576 {
76- UnityArchive archive = null ;
77-
78- try
79- {
80- archive = UnityFileSystem . MountArchive ( file , "archive:" + Path . DirectorySeparatorChar ) ;
81- }
82- catch ( NotSupportedException )
83- {
84- // It wasn't an AssetBundle, try to open the file as a SerializedFile.
85-
86- var relativePath = Path . GetRelativePath ( rootDirectory , file ) ;
87- m_Writer . WriteSerializedFile ( relativePath , file , Path . GetDirectoryName ( file ) ) ;
88- }
89-
90- if ( archive != null )
77+ if ( IsUnityArchive ( file ) )
9178 {
92- try
79+ using ( UnityArchive archive = UnityFileSystem . MountArchive ( file , "archive:" + Path . DirectorySeparatorChar ) )
9380 {
94- var assetBundleName = Path . GetRelativePath ( rootDirectory , file ) ;
81+ if ( archive == null )
82+ throw new FileLoadException ( $ "Failed to mount archive: { file } ") ;
9583
96- m_Writer . BeginAssetBundle ( assetBundleName , new FileInfo ( file ) . Length ) ;
97-
98- foreach ( var node in archive . Nodes )
84+ try
9985 {
100- if ( node . Flags . HasFlag ( ArchiveNodeFlags . SerializedFile ) )
86+ var assetBundleName = Path . GetRelativePath ( rootDirectory , file ) ;
87+
88+ m_Writer . BeginAssetBundle ( assetBundleName , new FileInfo ( file ) . Length ) ;
89+
90+ foreach ( var node in archive . Nodes )
10191 {
102- try
103- {
104- m_Writer . WriteSerializedFile ( node . Path , "archive:/" + node . Path , Path . GetDirectoryName ( file ) ) ;
105- }
106- catch ( Exception e )
92+ if ( node . Flags . HasFlag ( ArchiveNodeFlags . SerializedFile ) )
10793 {
108- Console . Error . WriteLine ( $ "Error processing { node . Path } in archive { file } ") ;
109- Console . Error . WriteLine ( e ) ;
110- Console . WriteLine ( ) ;
94+ try
95+ {
96+ m_Writer . WriteSerializedFile ( node . Path , "archive:/" + node . Path , Path . GetDirectoryName ( file ) ) ;
97+ }
98+ catch ( Exception e )
99+ {
100+ // the most likely exception here is Microsoft.Data.Sqlite.SqliteException,
101+ // for example 'UNIQUE constraint failed: serialized_files.id'.
102+ // or 'UNIQUE constraint failed: objects.id' which can happen
103+ // if AssetBundles from different builds are being processed by a single call to Analyze
104+ // or if there is a Unity Data Tool bug.
105+ Console . Error . WriteLine ( $ "Error processing { node . Path } in archive { file } ") ;
106+ Console . Error . WriteLine ( e . Message ) ;
107+ Console . WriteLine ( ) ;
108+
109+ // It is possible some files inside an archive will pass and others will fail, to have a partial analyze.
110+ // Overall that is reported as a failure
111+ successful = false ;
112+ }
111113 }
112114 }
113115 }
116+ finally
117+ {
118+ m_Writer . EndAssetBundle ( ) ;
119+ }
114120 }
115- finally
116- {
117- m_Writer . EndAssetBundle ( ) ;
118- archive . Dispose ( ) ;
119- }
121+ }
122+ else
123+ {
124+ // This isn't a Unity Archive file. Try to open it as a SerializedFile.
125+ // Unfortunately there is no standard file extension, or clear signature at the start of the file,
126+ // to test if it truly is a SerializedFile. So this will process files that are clearly not unity build files,
127+ // and there is a chance for crashes and freezes if the parser misinterprets the file content.
128+ var relativePath = Path . GetRelativePath ( rootDirectory , file ) ;
129+ m_Writer . WriteSerializedFile ( relativePath , file , Path . GetDirectoryName ( file ) ) ;
120130 }
121131 }
122132 catch ( NotSupportedException )
123133 {
124134 Console . Error . WriteLine ( ) ;
125135 //A "failed to load" error will already be logged by the UnityFileSystem library
136+
137+ successful = false ;
126138 }
127139 catch ( Exception e )
128140 {
@@ -131,6 +143,42 @@ void ProcessFile(string file, string rootDirectory)
131143 Console . WriteLine ( $ "{ e . GetType ( ) } : { e . Message } ") ;
132144 if ( Verbose )
133145 Console . WriteLine ( e . StackTrace ) ;
146+
147+ successful = false ;
148+ }
149+
150+ return successful ;
151+ }
152+
153+ private static bool IsUnityArchive ( string filePath )
154+ {
155+ // Check whether a file is a Unity Archive (AssetBundle) by looking for known signatures at the start of the file.
156+ // "UnifyFS" is the current signature, but some older formats of the file are still supported
157+ string [ ] signatures = { "UnityFS" , "UnityWeb" , "UnityRaw" , "UnityArchive" } ;
158+ int maxLen = 12 ; // "UnityArchive".Length
159+ byte [ ] buffer = new byte [ maxLen ] ;
160+
161+ using ( var fs = new FileStream ( filePath , FileMode . Open , FileAccess . Read , FileShare . Read ) )
162+ {
163+ int read = fs . Read ( buffer , 0 , buffer . Length ) ;
164+ foreach ( var sig in signatures )
165+ {
166+ if ( read >= sig . Length )
167+ {
168+ bool match = true ;
169+ for ( int i = 0 ; i < sig . Length ; ++ i )
170+ {
171+ if ( buffer [ i ] != sig [ i ] )
172+ {
173+ match = false ;
174+ break ;
175+ }
176+ }
177+ if ( match )
178+ return true ;
179+ }
180+ }
181+ return false ;
134182 }
135183 }
136184 }
0 commit comments