11using System ;
22using System . IO ;
3+ using System . Linq ;
34using NUnit . Framework ;
45using UnityDataTools . Analyzer . Util ;
56using UnityDataTools . FileSystem ;
@@ -189,6 +190,42 @@ public void TryParseMetadata_PlayerDataLevel0_ReturnsExpectedValues()
189190 Assert . That ( metadata . UnityVersion , Is . EqualTo ( "2022.1.20f1" ) , "Unity version should be 2022.1.20f1" ) ;
190191 Assert . That ( metadata . TargetPlatform , Is . EqualTo ( 2u ) , "Target platform should be 2 (Windows Standalone)" ) ;
191192 Assert . IsTrue ( metadata . EnableTypeTree , "EnableTypeTree should be true" ) ;
193+
194+ // --- TypeTree counts ---
195+ Assert . That ( metadata . TypeTreeCount , Is . EqualTo ( 10 ) , "Should have 10 regular type entries" ) ;
196+ Assert . That ( metadata . SerializedReferenceTypeTreeCount , Is . EqualTo ( 0 ) , "Should have 0 SerializeReference type entries" ) ;
197+ Assert . IsNotNull ( metadata . TypeTrees , "TypeTrees should be populated" ) ;
198+ Assert . That ( metadata . TypeTrees . Length , Is . EqualTo ( 10 ) ) ;
199+
200+ // --- Per-entry invariants for a player scene with no MonoBehaviours ---
201+ // All types are native Unity types: inline TypeTrees, no script IDs, no ref-type fields.
202+ foreach ( var entry in metadata . TypeTrees )
203+ {
204+ Assert . IsTrue ( entry . InlineTypeTree ,
205+ $ "InlineTypeTree should be true (persistentTypeID={ entry . PersistentTypeID } )") ;
206+ Assert . IsFalse ( entry . OldTypeHash . IsZero ,
207+ $ "OldTypeHash should not be zero (persistentTypeID={ entry . PersistentTypeID } )") ;
208+ Assert . IsTrue ( entry . TypeTreeContentHash . IsZero ,
209+ $ "TypeTreeContentHash should be zero for version < 23 (persistentTypeID={ entry . PersistentTypeID } )") ;
210+ Assert . Greater ( entry . TypeTreeSerializedSize , 0u ,
211+ $ "TypeTreeSerializedSize should be non-zero (persistentTypeID={ entry . PersistentTypeID } )") ;
212+ Assert . Greater ( entry . PersistentTypeID , 0 ,
213+ $ "PersistentTypeID should be positive for native types (got { entry . PersistentTypeID } )") ;
214+ Assert . AreNotEqual ( 114 , entry . PersistentTypeID ,
215+ "No MonoBehaviour types expected in this scene" ) ;
216+ Assert . That ( entry . ScriptTypeIndex , Is . EqualTo ( ( short ) - 1 ) ,
217+ $ "ScriptTypeIndex should be -1 for native types (persistentTypeID={ entry . PersistentTypeID } )") ;
218+ Assert . IsTrue ( entry . ScriptID . IsZero ,
219+ $ "ScriptID should be zero for native types (persistentTypeID={ entry . PersistentTypeID } )") ;
220+ Assert . That ( entry . ClassName , Is . EqualTo ( string . Empty ) ,
221+ $ "ClassName should be empty for non-ref types (persistentTypeID={ entry . PersistentTypeID } )") ;
222+ Assert . That ( entry . Namespace , Is . EqualTo ( string . Empty ) ,
223+ $ "Namespace should be empty for non-ref types (persistentTypeID={ entry . PersistentTypeID } )") ;
224+ Assert . That ( entry . AssemblyName , Is . EqualTo ( string . Empty ) ,
225+ $ "AssemblyName should be empty for non-ref types (persistentTypeID={ entry . PersistentTypeID } )") ;
226+ Assert . That ( entry . TypeDependencies . Length , Is . EqualTo ( 0 ) ,
227+ $ "TypeDependencies should be empty (persistentTypeID={ entry . PersistentTypeID } )") ;
228+ }
192229 }
193230
194231 [ Test ]
@@ -210,6 +247,101 @@ public void TryParseMetadata_PlayerNoTypeTreeLevel1_ReturnsExpectedValues()
210247 Assert . That ( metadata . UnityVersion , Is . EqualTo ( "6000.0.65f1" ) , "Unity version should be 6000.0.65f1" ) ;
211248 Assert . That ( metadata . TargetPlatform , Is . EqualTo ( 19u ) , "Target platform should be 19 (Windows Standalone x64)" ) ;
212249 Assert . IsFalse ( metadata . EnableTypeTree , "EnableTypeTree should be false for a no-type-tree build" ) ;
250+
251+ // Even when TypeTrees are not stored inline, the metadata still records the full list of
252+ // types used in the file along with their oldTypeHash values. The hashes allow the runtime
253+ // to verify type compatibility against its built-in type definitions at load time.
254+ Assert . That ( metadata . TypeTreeCount , Is . EqualTo ( 6 ) , "Should have 6 type entries" ) ;
255+ Assert . IsNotNull ( metadata . TypeTrees , "TypeTrees should be populated" ) ;
256+ Assert . That ( metadata . TypeTrees . Length , Is . EqualTo ( 6 ) ) ;
257+
258+ foreach ( var entry in metadata . TypeTrees )
259+ {
260+ Assert . Greater ( entry . PersistentTypeID , 0 ,
261+ $ "PersistentTypeID should be positive (got { entry . PersistentTypeID } )") ;
262+ Assert . IsFalse ( entry . OldTypeHash . IsZero ,
263+ $ "OldTypeHash should not be zero (persistentTypeID={ entry . PersistentTypeID } )") ;
264+ Assert . IsFalse ( entry . InlineTypeTree ,
265+ $ "InlineTypeTree should be false when EnableTypeTree=false (persistentTypeID={ entry . PersistentTypeID } )") ;
266+ Assert . IsTrue ( entry . TypeTreeContentHash . IsZero ,
267+ $ "TypeTreeContentHash should be zero for this version < 23 file (persistentTypeID={ entry . PersistentTypeID } )") ;
268+ }
269+ }
270+
271+ [ Test ]
272+ public void TryParseMetadata_V22PrefabWithSerializedReference_ReturnsExpectedTypeTreeData ( )
273+ {
274+ var testFile = Path . Combine ( m_TestDataPath , "AssetBundleTypeTreeVariations" , "v22" ,
275+ "prefab_with_serializedreference.serializedfile" ) ;
276+
277+ bool headerResult = SerializedFileDetector . TryDetectSerializedFile ( testFile , out var headerInfo ) ;
278+ Assert . IsTrue ( headerResult , "File should be detected as a valid SerializedFile" ) ;
279+
280+ bool result = SerializedFileDetector . TryParseMetadata ( testFile , headerInfo , out var metadata , out var errorMessage ) ;
281+ Assert . IsTrue ( result , $ "Metadata parsing should succeed. Error: { errorMessage } ") ;
282+ Assert . IsNotNull ( metadata ) ;
283+
284+ // --- Initial metadata fields ---
285+ Assert . IsTrue ( metadata . EnableTypeTree , "EnableTypeTree should be true" ) ;
286+
287+ // --- Type counts ---
288+ Assert . That ( metadata . TypeTreeCount , Is . EqualTo ( 4 ) , "Should have 4 regular type entries" ) ;
289+ Assert . That ( metadata . SerializedReferenceTypeTreeCount , Is . EqualTo ( 1 ) , "Should have 1 SerializeReference type entry" ) ;
290+ Assert . IsNotNull ( metadata . TypeTrees , "TypeTrees array should be populated" ) ;
291+ Assert . IsNotNull ( metadata . SerializedReferenceTypeTrees , "SerializedReferenceTypeTrees array should be populated" ) ;
292+
293+ // --- Regular type entries: persistentTypeIDs in order ---
294+ int [ ] expectedTypeIDs = { 142 , 4 , 1 , 114 } ;
295+ Assert . That ( metadata . TypeTrees . Length , Is . EqualTo ( expectedTypeIDs . Length ) ) ;
296+ for ( int i = 0 ; i < expectedTypeIDs . Length ; i ++ )
297+ Assert . That ( metadata . TypeTrees [ i ] . PersistentTypeID , Is . EqualTo ( expectedTypeIDs [ i ] ) ,
298+ $ "TypeTrees[{ i } ].PersistentTypeID") ;
299+
300+ // --- v22 files do not store TypeTreeContentHash (it is all-zeros) ---
301+ foreach ( var entry in metadata . TypeTrees )
302+ Assert . IsTrue ( entry . TypeTreeContentHash . IsZero ,
303+ $ "TypeTreeContentHash should be zero for v22 (persistentTypeID={ entry . PersistentTypeID } )") ;
304+ foreach ( var entry in metadata . SerializedReferenceTypeTrees )
305+ Assert . IsTrue ( entry . TypeTreeContentHash . IsZero ,
306+ "SerializedReferenceTypeTrees TypeTreeContentHash should be zero for v22" ) ;
307+
308+ // --- All type trees are inline (non-zero size, InlineTypeTree=true) ---
309+ foreach ( var entry in metadata . TypeTrees )
310+ {
311+ Assert . IsTrue ( entry . InlineTypeTree ,
312+ $ "InlineTypeTree should be true (persistentTypeID={ entry . PersistentTypeID } )") ;
313+ Assert . Greater ( entry . TypeTreeSerializedSize , 0u ,
314+ $ "TypeTreeSerializedSize should be non-zero (persistentTypeID={ entry . PersistentTypeID } )") ;
315+ }
316+ foreach ( var entry in metadata . SerializedReferenceTypeTrees )
317+ {
318+ Assert . IsTrue ( entry . InlineTypeTree , "SerializedReferenceTypeTrees[0].InlineTypeTree should be true" ) ;
319+ Assert . Greater ( entry . TypeTreeSerializedSize , 0u ,
320+ "SerializedReferenceTypeTrees[0].TypeTreeSerializedSize should be non-zero" ) ;
321+ }
322+
323+ // --- MonoBehaviour (114) has special entries because it refers to a specific C# class ---
324+ // Note: if multiple C# MonoBehaviour-derived types were used in this serialized files then we would have multiple entries.
325+ var monoBehaviour = metadata . TypeTrees . First ( t => t . PersistentTypeID == 114 ) ;
326+ Assert . IsFalse ( monoBehaviour . ScriptID . IsZero ,
327+ "MonoBehaviour type entry should carry a non-zero scriptID" ) ;
328+
329+ Assert . That ( monoBehaviour . ScriptTypeIndex , Is . EqualTo ( 0 ) ,
330+ "MonoBehaviour type entry should have a valid ScriptTypeIndex" ) ; // -1 is used for non-script types, so 0 is the first valid index
331+
332+ Assert . That ( monoBehaviour . TypeDependencies . Length , Is . EqualTo ( 1 ) ,
333+ "MonoBehaviour should have TypeDependencies array because to record SerializedReference dependencies" ) ;
334+
335+ Assert . That ( monoBehaviour . TypeDependencies [ 0 ] , Is . EqualTo ( 0 ) ,
336+ "MonoBehaviour should record dependency on SerializedReference" ) ;
337+
338+ // --- SerializedReference type entry ---
339+ Assert . That ( metadata . SerializedReferenceTypeTrees . Length , Is . EqualTo ( 1 ) ) ;
340+ var refType = metadata . SerializedReferenceTypeTrees [ 0 ] ;
341+ Assert . That ( refType . PersistentTypeID , Is . EqualTo ( - 1 ) ) ;
342+ Assert . That ( refType . ClassName , Is . EqualTo ( "Data" ) ) ;
343+ Assert . That ( refType . Namespace , Is . EqualTo ( "MyScripts" ) ) ;
344+ Assert . That ( refType . AssemblyName , Is . EqualTo ( "Assembly-CSharp" ) ) ;
213345 }
214346
215347 #endregion
@@ -287,7 +419,7 @@ public void IsUnityArchive_ValidAssetBundle_ReturnsTrue()
287419 [ Test ]
288420 public void IsUnityArchive_OldFormatArchive_ReturnsTrue ( )
289421 {
290- var testFile = Path . Combine ( m_TestDataPath , "LegacyFormats" , "alienprefab" ) ;
422+ var testFile = Path . Combine ( m_TestDataPath , "LegacyFormats" , "AssetBundles" , " alienprefab") ;
291423
292424 bool result = ArchiveDetector . IsUnityArchive ( testFile ) ;
293425
0 commit comments