Skip to content

Commit 2991315

Browse files
[#77] dump: detect missing TypeTrees up front instead of crashing
dump can never interpret a SerializedFile without TypeTrees, yet it handed such files to the native loader, producing misleading version mismatch errors and a raw SerializedFileOpenException with a stack trace (and, for archives, no clean handling at all). Detect missing TypeTrees before opening for both the direct-SerializedFile and the Unity Archive paths, and print a clear message explaining dump cannot process the file. This reuses the same SerializedFileDetector.IsMissingTypeTrees check the analyzer uses. With the dump path no longer relying on it, the now-unused GetOpenFailureHint and MissingTypeTreesHint are removed from SerializedFileDetector.
1 parent 377a09e commit 2991315

3 files changed

Lines changed: 80 additions & 35 deletions

File tree

‎TextDumper/TextDumperTool.cs‎

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ public int Dump(DumpOptions options)
7676

7777
int DumpSerializedFile()
7878
{
79+
if (ReportIfMissingTypeTrees(m_Options.Path, m_Options.Path))
80+
return 1;
81+
7982
try
8083
{
8184
if (m_Options.ToStdout)
@@ -93,23 +96,33 @@ int DumpSerializedFile()
9396
}
9497
catch (SerializedFileOpenException)
9598
{
96-
var hint = SerializedFileDetector.GetOpenFailureHint(m_Options.Path);
97-
if (hint != null)
98-
{
99-
Console.Error.WriteLine();
100-
Console.Error.WriteLine(hint);
101-
}
99+
Console.Error.WriteLine($"Error: Failed to open serialized file: {m_Options.Path}");
102100
return 1;
103101
}
104102

105103
return 0;
106104
}
107105

106+
// dump needs TypeTrees to interpret object data, so a SerializedFile without them cannot be dumped.
107+
// Detecting this up front avoids handing the file to the native loader, which would otherwise emit
108+
// misleading version mismatch errors or crash the process. Returns true (and prints a clear message)
109+
// when the file has no TypeTrees. The path may be a real file or an entry in a mounted archive.
110+
bool ReportIfMissingTypeTrees(string path, string displayName)
111+
{
112+
using var stream = new UnityFileStream(path);
113+
if (!SerializedFileDetector.IsMissingTypeTrees(stream))
114+
return false;
115+
116+
Console.Error.WriteLine($"Error: \"{displayName}\" has no TypeTrees. The dump command needs TypeTrees to interpret the serialized object data, so this file cannot be dumped.");
117+
return true;
118+
}
119+
108120
// For convenience we also support directly dumping serialized files that are inside an archive,
109121
// so that it's not necessary to use `archive extract` if you only want to see values from the object serialization.
110122
int DumpArchive()
111123
{
112124
using var archive = UnityFileSystem.MountArchive(m_Options.Path, "/");
125+
bool anyMissingTypeTrees = false;
113126

114127
if (m_Options.ToStdout)
115128
{
@@ -139,6 +152,8 @@ int DumpArchive()
139152

140153
var node2 = singleSerializedFile.Value;
141154
Console.Error.WriteLine($"Processing {node2.Path} {node2.Size} {node2.Flags}");
155+
if (ReportIfMissingTypeTrees("/" + node2.Path, node2.Path))
156+
return 1;
142157
m_Writer = Console.Out;
143158
OutputSerializedFile("/" + node2.Path);
144159
m_Writer.Flush();
@@ -151,14 +166,20 @@ int DumpArchive()
151166

152167
if (node.Flags.HasFlag(ArchiveNodeFlags.SerializedFile))
153168
{
169+
if (ReportIfMissingTypeTrees("/" + node.Path, node.Path))
170+
{
171+
anyMissingTypeTrees = true;
172+
continue;
173+
}
174+
154175
using var writer = new StreamWriter(Path.Combine(m_Options.OutputPath, Path.GetFileName(node.Path) + ".txt"), false);
155176
m_Writer = writer;
156177
OutputSerializedFile("/" + node.Path);
157178
}
158179
}
159180
}
160181

161-
return 0;
182+
return anyMissingTypeTrees ? 1 : 0;
162183
}
163184

164185
void OutputSerializedFile(string path)

‎UnityBinaryFormat/SerializedFileDetector.cs‎

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -655,16 +655,6 @@ public static bool TryParseMetadata(Stream stream, SerializedFileInfo headerInfo
655655
}
656656
}
657657

658-
/// <summary>
659-
/// Explanation shown for SerializedFiles that have no TypeTrees. Such files can only be
660-
/// opened when every type they use exactly matches the build of UnityFileSystemApi in use,
661-
/// which is why the native loader reports misleading version mismatch errors (or crashes)
662-
/// when that is not the case.
663-
/// </summary>
664-
public const string MissingTypeTreesHint =
665-
"Note: This file does not have TypeTrees and can only be opened if all the " +
666-
"types it uses exactly match the types in the build of UnityFileSystemApi being used.";
667-
668658
/// <summary>
669659
/// Returns true when the stream is a SerializedFile we can positively confirm has no TypeTrees.
670660
/// Returns false for files that have TypeTrees and for anything we cannot parse (so callers fall
@@ -677,24 +667,6 @@ public static bool IsMissingTypeTrees(Stream stream)
677667
&& !metadata.EnableTypeTree;
678668
}
679669

680-
/// <summary>
681-
/// Returns a diagnostic hint explaining why a SerializedFile may have failed to open,
682-
/// or null if no specific diagnosis is available.
683-
/// Currently detects the common case of missing TypeTrees (player builds compiled
684-
/// without type information, which the DLL reports as a generic unknown error).
685-
/// </summary>
686-
/// <param name="path">Real filesystem path to the file that failed to open.</param>
687-
public static string GetOpenFailureHint(string path)
688-
{
689-
if (TryDetectSerializedFile(path, out var fileInfo) &&
690-
TryParseMetadata(path, fileInfo, out var metadata, out _) &&
691-
!metadata.EnableTypeTree)
692-
{
693-
return MissingTypeTreesHint;
694-
}
695-
return null;
696-
}
697-
698670
/// <summary>
699671
/// Parses the TypeTree and other arrays that are stored in the metadata,
700672
/// </summary>

‎UnityDataTool.Tests/DumpTests.cs‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ public class DumpTests
1313
private string m_SerializedFilePath;
1414
private string m_ResourceFilePath;
1515
private string m_MultiSerializedFileArchivePath;
16+
private string m_NoTypeTreeSerializedFilePath;
17+
private string m_NoTypeTreeArchivePath;
1618

1719
[OneTimeSetUp]
1820
public void OneTimeSetup()
@@ -21,6 +23,8 @@ public void OneTimeSetup()
2123
m_SerializedFilePath = Path.Combine(m_TestDataFolder, "PlayerWithTypeTrees", "level0");
2224
m_ResourceFilePath = Path.Combine(m_TestDataFolder, "PlayerWithTypeTrees", "sharedassets0.assets.resS");
2325
m_MultiSerializedFileArchivePath = Path.Combine(m_TestDataFolder, "PlayerDataCompressed", "data.unity3d");
26+
m_NoTypeTreeSerializedFilePath = Path.Combine(m_TestDataFolder, "PlayerNoTypeTree", "level0");
27+
m_NoTypeTreeArchivePath = Path.Combine(m_TestDataFolder, "AssetBundleTypeTreeVariations", "AssetBundle-NoTypeTree", "small.bundle");
2428
}
2529

2630
[Test]
@@ -207,4 +211,52 @@ public async Task Dump_Stdout_InvalidFileType_Fails()
207211

208212
Assert.That(swErr.ToString(), Does.Contain("does not appear to be a valid Unity SerializedFile or Unity Archive"));
209213
}
214+
215+
[Test]
216+
public async Task Dump_NoTypeTreeSerializedFile_ReportsMissingTypeTreesWithoutCrashing()
217+
{
218+
using var swOut = new StringWriter();
219+
using var swErr = new StringWriter();
220+
var currentOut = Console.Out;
221+
var currentErr = Console.Error;
222+
try
223+
{
224+
Console.SetOut(swOut);
225+
Console.SetError(swErr);
226+
Assert.AreNotEqual(0, await Program.Main(new string[] { "dump", m_NoTypeTreeSerializedFilePath, "--stdout" }));
227+
}
228+
finally
229+
{
230+
Console.SetOut(currentOut);
231+
Console.SetError(currentErr);
232+
}
233+
234+
var output = swOut.ToString() + swErr.ToString();
235+
Assert.That(output, Does.Contain("has no TypeTrees"), "Expected a clear missing-TypeTrees message");
236+
Assert.That(output, Does.Not.Contain("SerializedFileOpenException"), "Should not leak an exception/stack trace");
237+
}
238+
239+
[Test]
240+
public async Task Dump_NoTypeTreeArchive_ReportsMissingTypeTreesWithoutCrashing()
241+
{
242+
using var swOut = new StringWriter();
243+
using var swErr = new StringWriter();
244+
var currentOut = Console.Out;
245+
var currentErr = Console.Error;
246+
try
247+
{
248+
Console.SetOut(swOut);
249+
Console.SetError(swErr);
250+
Assert.AreNotEqual(0, await Program.Main(new string[] { "dump", m_NoTypeTreeArchivePath, "--stdout" }));
251+
}
252+
finally
253+
{
254+
Console.SetOut(currentOut);
255+
Console.SetError(currentErr);
256+
}
257+
258+
var output = swOut.ToString() + swErr.ToString();
259+
Assert.That(output, Does.Contain("has no TypeTrees"), "Expected a clear missing-TypeTrees message");
260+
Assert.That(output, Does.Not.Contain("SerializedFileOpenException"), "Should not leak an exception/stack trace");
261+
}
210262
}

0 commit comments

Comments
 (0)