@@ -65,64 +65,76 @@ bool ShouldIgnoreFile(string file)
65
65
66
66
private static readonly HashSet < string > IgnoredExtensions = new ( )
67
67
{
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"
69
70
} ;
70
71
71
-
72
- void ProcessFile ( string file , string rootDirectory )
72
+ bool ProcessFile ( string file , string rootDirectory )
73
73
{
74
+ bool successful = true ;
74
75
try
75
76
{
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 ) )
91
78
{
92
- try
79
+ using ( UnityArchive archive = UnityFileSystem . MountArchive ( file , "archive:" + Path . DirectorySeparatorChar ) )
93
80
{
94
- var assetBundleName = Path . GetRelativePath ( rootDirectory , file ) ;
81
+ if ( archive == null )
82
+ throw new FileLoadException ( $ "Failed to mount archive: { file } ") ;
95
83
96
- m_Writer . BeginAssetBundle ( assetBundleName , new FileInfo ( file ) . Length ) ;
97
-
98
- foreach ( var node in archive . Nodes )
84
+ try
99
85
{
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 )
101
91
{
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 ) )
107
93
{
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
+ }
111
113
}
112
114
}
113
115
}
116
+ finally
117
+ {
118
+ m_Writer . EndAssetBundle ( ) ;
119
+ }
114
120
}
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 ) ) ;
120
130
}
121
131
}
122
132
catch ( NotSupportedException )
123
133
{
124
134
Console . Error . WriteLine ( ) ;
125
135
//A "failed to load" error will already be logged by the UnityFileSystem library
136
+
137
+ successful = false ;
126
138
}
127
139
catch ( Exception e )
128
140
{
@@ -131,6 +143,42 @@ void ProcessFile(string file, string rootDirectory)
131
143
Console . WriteLine ( $ "{ e . GetType ( ) } : { e . Message } ") ;
132
144
if ( Verbose )
133
145
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 ;
134
182
}
135
183
}
136
184
}
0 commit comments