@@ -303,16 +303,37 @@ public static bool TryDetectSerializedFile(string filePath, out SerializedFileIn
303303 try
304304 {
305305 using var stream = new FileStream ( filePath , FileMode . Open , FileAccess . Read , FileShare . Read ) ;
306+ return TryDetectSerializedFile ( stream , out info ) ;
307+ }
308+ catch
309+ {
310+ return false ;
311+ }
312+ }
313+
314+ /// <summary>
315+ /// Stream-based variant of <see cref="TryDetectSerializedFile(string, out SerializedFileInfo)"/>.
316+ /// Reads from the current contents of <paramref name="stream"/> (seeking it to the start first),
317+ /// allowing detection of files that are not directly on disk (e.g. inside a mounted archive).
318+ /// </summary>
319+ public static bool TryDetectSerializedFile ( Stream stream , out SerializedFileInfo info )
320+ {
321+ info = null ;
322+
323+ try
324+ {
306325 long fileLength = stream . Length ;
307326
308327 // Quick rejection: file must be at least large enough for the legacy header
309328 if ( fileLength < LegacyHeaderSize )
310329 return false ;
311330
331+ stream . Seek ( 0 , SeekOrigin . Begin ) ;
332+
312333 // Read enough bytes to cover a modern header (48 bytes)
313334 // We'll determine which format to parse based on the version field
314335 byte [ ] headerBytes = new byte [ ModernHeaderSize ] ;
315- int bytesRead = stream . Read ( headerBytes , 0 , headerBytes . Length ) ;
336+ int bytesRead = stream . ReadAtLeast ( headerBytes , ModernHeaderSize , throwOnEndOfStream : false ) ;
316337
317338 if ( bytesRead < LegacyHeaderSize )
318339 return false ;
@@ -528,32 +549,71 @@ public static bool TryParseMetadata(string filePath, SerializedFileInfo headerIn
528549 metadata = null ;
529550 errorMessage = null ;
530551
552+ // The supported-version check depends only on the header, so do it before touching the file.
553+ if ( ! IsMetadataVersionSupported ( headerInfo . Version , out errorMessage ) )
554+ return false ;
555+
556+ try
557+ {
558+ using var stream = new FileStream ( filePath , FileMode . Open , FileAccess . Read , FileShare . Read ) ;
559+ return TryParseMetadata ( stream , headerInfo , out metadata , out errorMessage ) ;
560+ }
561+ catch
562+ {
563+ errorMessage = "An unexpected error occurred while opening the file." ;
564+ return false ;
565+ }
566+ }
567+
568+ /// <summary>
569+ /// Validates that the SerializedFile version is within the range whose metadata layout this
570+ /// parser understands. Returns false with an explanatory message when it is not.
571+ /// </summary>
572+ private static bool IsMetadataVersionSupported ( uint version , out string errorMessage )
573+ {
531574 // Only support version >= 19 (Unity 2019.1). Older files have metadata format
532575 // differences we have not implemented.
533- if ( headerInfo . Version < MinMetadataParseVersion )
576+ if ( version < MinMetadataParseVersion )
534577 {
535- errorMessage = $ "Metadata parsing is not supported for SerializedFile version { headerInfo . Version } . " +
578+ errorMessage = $ "Metadata parsing is not supported for SerializedFile version { version } . " +
536579 $ "Version { MinMetadataParseVersion } (Unity 2019.1) or newer is required.";
537580 return false ;
538581 }
539582
540583 // Reject versions beyond the highest known format. Future Unity versions may change the
541584 // metadata layout in ways that would cause incorrect results or a parse failure.
542585 // A newer version of UnityDataTool is required to read these files.
543- if ( headerInfo . Version > MaxMetadataParseVersion )
586+ if ( version > MaxMetadataParseVersion )
544587 {
545- errorMessage = $ "SerializedFile version { headerInfo . Version } is not supported. " +
588+ errorMessage = $ "SerializedFile version { version } is not supported. " +
546589 $ "UnityDataTool supports up to version { MaxMetadataParseVersion } . " +
547590 $ "Please use a newer version of UnityDataTool to read this file.";
548591 return false ;
549592 }
550593
594+ errorMessage = null ;
595+ return true ;
596+ }
597+
598+ /// <summary>
599+ /// Stream-based variant of <see cref="TryParseMetadata(string, SerializedFileInfo, out SerializedFileMetadata, out string)"/>.
600+ /// When <paramref name="parseExtended"/> is false, only the leading metadata fields (Unity version,
601+ /// target platform and EnableTypeTree) are read; the type/object/reference arrays are skipped. This
602+ /// is the cheap path for callers that only need to know whether the file has TypeTrees.
603+ /// </summary>
604+ public static bool TryParseMetadata ( Stream stream , SerializedFileInfo headerInfo , out SerializedFileMetadata metadata , out string errorMessage , bool parseExtended = true )
605+ {
606+ metadata = null ;
607+ errorMessage = null ;
608+
609+ if ( ! IsMetadataVersionSupported ( headerInfo . Version , out errorMessage ) )
610+ return false ;
611+
551612 try
552613 {
553614 long metadataOffset = headerInfo . IsLegacyFormat ? LegacyHeaderSize : ModernHeaderSize ;
554615 bool swap = headerInfo . Endianness == BigEndian ;
555616
556- using var stream = new FileStream ( filePath , FileMode . Open , FileAccess . Read , FileShare . Read ) ;
557617 stream . Seek ( metadataOffset , SeekOrigin . Begin ) ;
558618 using var reader = new BinaryReader ( stream , System . Text . Encoding . ASCII , leaveOpen : true ) ;
559619
@@ -583,7 +643,8 @@ public static bool TryParseMetadata(string filePath, SerializedFileInfo headerIn
583643
584644 // Parse the rest of the metadata section. Protected by its own try/catch so that any
585645 // failure there still returns a partially-populated metadata struct.
586- ParseExtendedMetadata ( reader , headerInfo , swap , metadataOffset , metadata ) ;
646+ if ( parseExtended )
647+ ParseExtendedMetadata ( reader , headerInfo , swap , metadataOffset , metadata ) ;
587648
588649 return true ;
589650 }
@@ -595,22 +656,15 @@ public static bool TryParseMetadata(string filePath, SerializedFileInfo headerIn
595656 }
596657
597658 /// <summary>
598- /// Returns a diagnostic hint explaining why a SerializedFile may have failed to open,
599- /// or null if no specific diagnosis is available.
600- /// Currently detects the common case of missing TypeTrees (player builds compiled
601- /// without type information, which the DLL reports as a generic unknown error).
659+ /// Returns true when the stream is a SerializedFile we can positively confirm has no TypeTrees.
660+ /// Returns false for files that have TypeTrees and for anything we cannot parse (so callers fall
661+ /// back to the normal open path rather than skipping a file we simply did not understand).
602662 /// </summary>
603- /// <param name="path">Real filesystem path to the file that failed to open.</param>
604- public static string GetOpenFailureHint ( string path )
663+ public static bool IsMissingTypeTrees ( Stream stream )
605664 {
606- if ( TryDetectSerializedFile ( path , out var fileInfo ) &&
607- TryParseMetadata ( path , fileInfo , out var metadata , out _ ) &&
608- ! metadata . EnableTypeTree )
609- {
610- return "Note: This file does not have TypeTrees and can only be opened if all the " +
611- "types it uses exactly match the types in the build of UnityFileSystemApi being used." ;
612- }
613- return null ;
665+ return TryDetectSerializedFile ( stream , out var fileInfo )
666+ && TryParseMetadata ( stream , fileInfo , out var metadata , out _ , parseExtended : false )
667+ && ! metadata . EnableTypeTree ;
614668 }
615669
616670 /// <summary>
0 commit comments