Skip to content

Commit 7982401

Browse files
Parse external file table directly from metadata
Similar to objectlist change
1 parent 5a5f637 commit 7982401

3 files changed

Lines changed: 73 additions & 34 deletions

File tree

Analyzer.Tests/FileDetectionTests.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,28 @@ public void TryParseMetadata_V22PrefabWithSerializedReference_ReturnsExpectedObj
448448
}
449449
}
450450

451+
[Test]
452+
public void TryParseMetadata_V22PrefabWithSerializedReference_ReturnsExpectedExternalReferences()
453+
{
454+
var testFile = Path.Combine(m_TestDataPath, "AssetBundleTypeTreeVariations", "v22",
455+
"prefab_with_serializedreference.serializedfile");
456+
457+
bool headerResult = SerializedFileDetector.TryDetectSerializedFile(testFile, out var headerInfo);
458+
Assert.IsTrue(headerResult, "File should be detected as a valid SerializedFile");
459+
460+
bool result = SerializedFileDetector.TryParseMetadata(testFile, headerInfo, out var metadata, out var errorMessage);
461+
Assert.IsTrue(result, $"Metadata parsing should succeed. Error: {errorMessage}");
462+
Assert.IsNotNull(metadata);
463+
464+
Assert.IsNotNull(metadata.ExternalReferences, "ExternalReferences should be populated");
465+
Assert.That(metadata.ExternalReferences.Length, Is.EqualTo(1), "Should have 1 external reference");
466+
467+
var extRef = metadata.ExternalReferences[0];
468+
Assert.That(extRef.Path, Is.EqualTo("archive:/CAB-d57a1d89ac0708bf030936c59479c685/CAB-d57a1d89ac0708bf030936c59479c685"));
469+
Assert.That(extRef.Guid, Is.EqualTo("00000000000000000000000000000000"));
470+
Assert.That(extRef.Type, Is.EqualTo(ExternalReferenceType.NonAssetType));
471+
}
472+
451473
#endregion
452474

453475
#region YAML SerializedFile Detection Tests

Analyzer/Util/SerializedFileDetector.cs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System;
22
using System.IO;
3+
using ExternalReference = UnityDataTools.FileSystem.ExternalReference;
4+
using ExternalReferenceType = UnityDataTools.FileSystem.ExternalReferenceType;
35
using ObjectInfo = UnityDataTools.FileSystem.ObjectInfo;
46

57
namespace UnityDataTools.Analyzer.Util;
@@ -173,6 +175,12 @@ public class SerializedFileMetadata
173175
/// Null until the metadata section has been parsed.
174176
/// </summary>
175177
public ObjectInfo[] ObjectList { get; set; }
178+
179+
/// <summary>
180+
/// List of external file references recorded in the file's externals table.
181+
/// Null until the metadata section has been parsed.
182+
/// </summary>
183+
public ExternalReference[] ExternalReferences { get; set; }
176184
}
177185

178186
/// <summary>
@@ -617,20 +625,28 @@ private static void ParseExtendedMetadata(BinaryReader reader, SerializedFileInf
617625
stream.Seek(8, SeekOrigin.Current); // int64 localIdentifierInFile
618626
}
619627

620-
// --- Skip the externals list ---
628+
// --- External references list ---
621629
// Per-entry layout:
622630
// [null-terminated string tempEmpty]
623631
// [uint32[4] guid] (16 bytes)
624632
// [int32 type]
625633
// [null-terminated string pathName]
626634
int externalsCount = BinaryFileHelper.ReadInt32(reader, swap);
635+
var externalRefs = new ExternalReference[externalsCount];
627636
for (int i = 0; i < externalsCount; i++)
628637
{
629-
BinaryFileHelper.ReadNullTermString(reader); // tempEmpty (empty in practice)
630-
stream.Seek(16, SeekOrigin.Current); // Hash128 guid (4 * uint32)
631-
stream.Seek(4, SeekOrigin.Current); // int32 type
632-
BinaryFileHelper.ReadNullTermString(reader); // pathName
638+
BinaryFileHelper.ReadNullTermString(reader); // tempEmpty (empty in practice)
639+
var guid = BinaryFileHelper.ReadHash128(reader, swap);
640+
int typeInt = BinaryFileHelper.ReadInt32(reader, swap);
641+
string pathName = BinaryFileHelper.ReadNullTermString(reader);
642+
externalRefs[i] = new ExternalReference
643+
{
644+
Path = pathName,
645+
Guid = guid.ToString(),
646+
Type = (ExternalReferenceType)typeInt,
647+
};
633648
}
649+
metadata.ExternalReferences = externalRefs;
634650

635651
// m_RefTypes (version >= 20) is not located immediately after m_Types.
636652
// It appears at the end of the metadata section

UnityDataTool/SerializedFileCommands.cs

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,39 +11,43 @@ public static class SerializedFileCommands
1111
{
1212
public static int HandleExternalRefs(FileInfo filename, OutputFormat format)
1313
{
14-
if (!ValidateSerializedFile(filename.FullName, out _))
14+
// External references are read directly from the parsed metadata rather than via UnityFileSystemApi.
15+
//
16+
// Advantages: works for any modern SerializedFile (version >= 19), including Player builds
17+
// that were compiled without TypeTrees — files that UnityFileSystemApi cannot open at all.
18+
//
19+
// Trade-offs: Files older than version 19 (Unity 2019.1) are not supported by the metadata parser.
20+
//
21+
// These trade-offs are minor compared to the benefit of handling the common no-TypeTree case,
22+
// so there is no need to keep the UnityFileSystemApi code path.
23+
if (!ValidateSerializedFile(filename.FullName, out var fileInfo))
1524
return 1;
1625

17-
try
26+
if (!SerializedFileDetector.TryParseMetadata(filename.FullName, fileInfo, out var metadata, out var errorMessage))
1827
{
19-
using var sf = UnityFileSystem.OpenSerializedFile(filename.FullName);
20-
if (format == OutputFormat.Json)
21-
OutputExternalRefsJson(sf);
22-
else
23-
OutputExternalRefsText(sf);
24-
return 0;
28+
Console.Error.WriteLine($"Error: Failed to parse external references for: {filename.FullName}");
29+
Console.Error.WriteLine(errorMessage);
30+
return 1;
2531
}
26-
catch (Exception err) when (err is NotSupportedException || err is FileFormatException)
32+
33+
if (metadata.ExternalReferences == null)
2734
{
28-
Console.Error.WriteLine($"Error opening SerializedFile: {filename.FullName}");
29-
Console.Error.WriteLine(err.Message);
35+
Console.Error.WriteLine($"Error: External references could not be parsed for: {filename.FullName}");
3036
return 1;
3137
}
38+
39+
if (format == OutputFormat.Json)
40+
OutputExternalRefsJson(metadata.ExternalReferences);
41+
else
42+
OutputExternalRefsText(metadata.ExternalReferences);
43+
44+
return 0;
3245
}
3346

3447
public static int HandleObjectList(FileInfo filename, OutputFormat format)
3548
{
3649
// The object list is read directly from the parsed metadata rather than via UnityFileSystemApi.
37-
//
38-
// Advantages: works for any modern SerializedFile (version >= 19), including Player builds
39-
// that were compiled without TypeTrees — files that UnityFileSystemApi cannot open at all.
40-
//
41-
// Trade-offs: type names come from TypeIdRegistry rather than the file's embedded TypeTree,
42-
// so uncommon types not covered by the registry are displayed as a numeric TypeId. Files
43-
// older than version 19 (Unity 2019.1) are not supported by the metadata parser.
44-
//
45-
// These trade-offs are minor compared to the benefit of handling the common no-TypeTree case,
46-
// so there is no need to keep the UnityFileSystemApi code path.
50+
// (See comment in HandleExternalRefs() for the reasons for doing it that way)
4751
if (!ValidateSerializedFile(filename.FullName, out var fileInfo))
4852
return 1;
4953

@@ -149,24 +153,21 @@ private static bool ValidateSerializedFile(string filePath, out SerializedFileIn
149153
return true;
150154
}
151155

152-
private static void OutputExternalRefsText(SerializedFile sf)
156+
private static void OutputExternalRefsText(ExternalReference[] refs)
153157
{
154-
var refs = sf.ExternalReferences;
155-
156-
for (int i = 0; i < refs.Count; i++)
158+
for (int i = 0; i < refs.Length; i++)
157159
{
158160
var extRef = refs[i];
159161
var displayValue = !string.IsNullOrEmpty(extRef.Path) ? extRef.Path : extRef.Guid;
160162
Console.WriteLine($"Index: {i + 1}, Path: {displayValue}");
161163
}
162164
}
163165

164-
private static void OutputExternalRefsJson(SerializedFile sf)
166+
private static void OutputExternalRefsJson(ExternalReference[] refs)
165167
{
166-
var refs = sf.ExternalReferences;
167-
var jsonArray = new object[refs.Count];
168+
var jsonArray = new object[refs.Length];
168169

169-
for (int i = 0; i < refs.Count; i++)
170+
for (int i = 0; i < refs.Length; i++)
170171
{
171172
var extRef = refs[i];
172173
jsonArray[i] = new

0 commit comments

Comments
 (0)