Skip to content

Commit c883beb

Browse files
authored
Add type filtering to dump command (Issue #52) (#63)
* Add type filtering to dump command (Issue #52) Added -t / --type option to the dump command. Accepts both numeric ClassID (-t 114) and type name (-t MonoBehaviour), case-insensitive. Resolved via TypeIdRegistry for built-in types, falls back to TypeTree root node for script types. Combines with -i as AND logic. Added "Filtering by Type" walkthrough in command-dump.md focused on the MonoBehaviour use case. Updated parameter docs in textdumper.md. Tests for name filter, numeric filter and no-match output. Made-with: Cursor
1 parent b6caf1e commit c883beb

5 files changed

Lines changed: 111 additions & 12 deletions

File tree

Documentation/command-dump.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ UnityDataTool dump <path> [options]
1515
| `-f, --output-format <format>` | Output format | `text` |
1616
| `-s, --skip-large-arrays` | Skip dumping large arrays | `false` |
1717
| `-i, --objectid <id>` | Only dump object with this ID | All objects |
18+
| `-t, --type <type>` | Filter by object type (ClassID number or type name) | All objects |
1819
| `-d, --typetree-data <file>` | Load an external TypeTree data file before processing (Unity 6.5+) ||
1920

2021
## Examples
@@ -39,6 +40,29 @@ Skip large arrays for cleaner output:
3940
UnityDataTool dump /path/to/file -s
4041
```
4142

43+
Dump only MonoBehaviour objects by type name:
44+
```bash
45+
UnityDataTool dump /path/to/file -t MonoBehaviour
46+
```
47+
48+
Same thing using the numeric ClassID:
49+
```bash
50+
UnityDataTool dump /path/to/file -t 114
51+
```
52+
53+
Dump the AssetBundle manifest object:
54+
```bash
55+
UnityDataTool dump mybundle -t AssetBundle
56+
```
57+
58+
---
59+
60+
## Filtering by Type
61+
62+
The `-t` / `--type` option filters output to objects of a specific Unity type. It accepts either a numeric ClassID (e.g. `114`) or a type name (e.g. `MonoBehaviour`). Type name matching is case-insensitive.
63+
64+
This is particularly useful for inspecting MonoBehaviour data in built AssetBundles. MonoBehaviour and ScriptableObject field values are serialized as binary, and a typical bundle contains many other object types (meshes, textures, materials, etc.). Using `-t MonoBehaviour` dumps only the scripting objects, showing the serialized C# field names, types, and values.
65+
4266
---
4367

4468
## Archive Support

Documentation/textdumper.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ file (AssetBundle or SerializedFile) into human-readable yaml-style text file.
55

66
## How to use
77

8-
The library consists of a single class called [TextDumperTool](../TextDumper/TextDumperTool.cs). It has a method named Dump and takes four parameters:
8+
The library consists of a single class called [TextDumperTool](../TextDumper/TextDumperTool.cs). It has a method named Dump and takes five parameters:
99
* path (string): path of the data file.
1010
* outputPath (string): path where the output files will be created.
1111
* skipLargeArrays (bool): if true, the content of arrays larger than 1KB won't be dumped.
1212
* objectId (long, optional): if specified and not 0, only the object with this signed 64-bit id will be dumped. If 0 (default), all objects are dumped.
13+
* typeFilter (string, optional): if specified, only objects matching this type are dumped. Accepts a numeric ClassID (e.g. 114) or a type name (e.g. MonoBehaviour, case-insensitive).
1314

1415
## How to interpret the output files
1516

TextDumper/TextDumperTool.cs

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@ public class TextDumperTool
1414
SerializedFile m_SerializedFile;
1515
StreamWriter m_Writer;
1616

17-
public int Dump(string path, string outputPath, bool skipLargeArrays, long objectId = 0)
17+
public int Dump(string path, string outputPath, bool skipLargeArrays, long objectId = 0, string typeFilter = null)
1818
{
19+
if (string.IsNullOrWhiteSpace(typeFilter))
20+
typeFilter = null;
21+
1922
m_SkipLargeArrays = skipLargeArrays;
2023

2124
try
@@ -38,7 +41,7 @@ public int Dump(string path, string outputPath, bool skipLargeArrays, long objec
3841
{
3942
using (m_Writer = new StreamWriter(Path.Combine(outputPath, Path.GetFileName(node.Path) + ".txt"), false))
4043
{
41-
OutputSerializedFile("/" + node.Path, objectId);
44+
OutputSerializedFile("/" + node.Path, objectId, typeFilter);
4245
}
4346
}
4447
}
@@ -56,7 +59,7 @@ public int Dump(string path, string outputPath, bool skipLargeArrays, long objec
5659
{
5760
using (m_Writer = new StreamWriter(Path.Combine(outputPath, Path.GetFileName(path) + ".txt"), false))
5861
{
59-
OutputSerializedFile(path, objectId);
62+
OutputSerializedFile(path, objectId, typeFilter);
6063
}
6164
}
6265
catch (SerializedFileOpenException)
@@ -378,8 +381,11 @@ bool DumpManagedReferenceData(TypeTreeNode refTypeNode, TypeTreeNode referencedT
378381
return true;
379382
}
380383

381-
void OutputSerializedFile(string path, long objectId)
384+
void OutputSerializedFile(string path, long objectId, string typeFilter)
382385
{
386+
int filterTypeId = 0;
387+
bool filterByTypeId = typeFilter != null && int.TryParse(typeFilter, out filterTypeId);
388+
383389
using (m_Reader = new UnityFileReader(path, 64 * 1024 * 1024))
384390
using (m_SerializedFile = UnityFileSystem.OpenSerializedFile(path))
385391
{
@@ -400,6 +406,26 @@ void OutputSerializedFile(string path, long objectId)
400406
continue;
401407

402408
var root = m_SerializedFile.GetTypeTreeRoot(obj.Id);
409+
410+
if (typeFilter != null)
411+
{
412+
if (filterByTypeId)
413+
{
414+
if (obj.TypeId != filterTypeId)
415+
continue;
416+
}
417+
else
418+
{
419+
var typeName = TypeIdRegistry.GetTypeName(obj.TypeId);
420+
// GetTypeName returns the id as a string when the type is unknown;
421+
// fall back to the TypeTree root node for script types.
422+
if (typeName == obj.TypeId.ToString())
423+
typeName = root.Type;
424+
if (!string.Equals(typeName, typeFilter, StringComparison.OrdinalIgnoreCase))
425+
continue;
426+
}
427+
}
428+
403429
var offset = obj.Offset;
404430

405431
m_Writer.Write($"ID: {obj.Id} (ClassID: {obj.TypeId}) ");
@@ -408,8 +434,13 @@ void OutputSerializedFile(string path, long objectId)
408434
dumpedObject = true;
409435
}
410436

411-
if (objectId != 0 && !dumpedObject)
412-
m_Writer.WriteLine($"Object with ID {objectId} not found.");
437+
if ((objectId != 0 || typeFilter != null) && !dumpedObject)
438+
{
439+
if (objectId != 0)
440+
m_Writer.WriteLine($"Object with ID {objectId} not found.");
441+
else
442+
m_Writer.WriteLine($"No objects found matching type \"{typeFilter}\".");
443+
}
413444
}
414445
}
415446

UnityDataTool.Tests/UnityDataToolAssetBundleTests.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,47 @@ public async Task DumpText_SkipLargeArrays_TextFileCreatedCorrectly(
144144
Assert.AreEqual(expected, content);
145145
}
146146

147+
[Test]
148+
public async Task DumpText_TypeFilterByName_OnlyMatchingObjectsDumped()
149+
{
150+
var path = Path.Combine(Context.UnityDataFolder, "assetbundle");
151+
var outputFile = Path.Combine(m_TestOutputFolder, "CAB-5d40f7cad7c871cf2ad2af19ac542994.txt");
152+
153+
Assert.AreEqual(0, await Program.Main(new string[] { "dump", path, "-t", "MonoBehaviour" }));
154+
Assert.IsTrue(File.Exists(outputFile));
155+
156+
var content = File.ReadAllText(outputFile);
157+
Assert.That(content, Does.Contain("(ClassID: 114)"));
158+
Assert.That(content, Does.Not.Contain("(ClassID: 1)"));
159+
}
160+
161+
[Test]
162+
public async Task DumpText_TypeFilterByClassID_OnlyMatchingObjectsDumped()
163+
{
164+
var path = Path.Combine(Context.UnityDataFolder, "assetbundle");
165+
var outputFile = Path.Combine(m_TestOutputFolder, "CAB-5d40f7cad7c871cf2ad2af19ac542994.txt");
166+
167+
Assert.AreEqual(0, await Program.Main(new string[] { "dump", path, "-t", "114" }));
168+
Assert.IsTrue(File.Exists(outputFile));
169+
170+
var content = File.ReadAllText(outputFile);
171+
Assert.That(content, Does.Contain("(ClassID: 114)"));
172+
Assert.That(content, Does.Not.Contain("(ClassID: 1)"));
173+
}
174+
175+
[Test]
176+
public async Task DumpText_TypeFilterNoMatch_ShowsNotFoundMessage()
177+
{
178+
var path = Path.Combine(Context.UnityDataFolder, "assetbundle");
179+
var outputFile = Path.Combine(m_TestOutputFolder, "CAB-5d40f7cad7c871cf2ad2af19ac542994.txt");
180+
181+
Assert.AreEqual(0, await Program.Main(new string[] { "dump", path, "-t", "NonExistentType" }));
182+
Assert.IsTrue(File.Exists(outputFile));
183+
184+
var content = File.ReadAllText(outputFile);
185+
Assert.That(content, Does.Contain("No objects found matching type"));
186+
}
187+
147188
[Test]
148189
public async Task Analyze_DefaultArgs_DatabaseCorrect()
149190
{

UnityDataTool/Program.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ public static async Task<int> Main(string[] args)
9292
var sOpt = new Option<bool>(aliases: new[] { "--skip-large-arrays", "-s" }, description: "Do not dump large arrays of basic data types");
9393
var oOpt = new Option<DirectoryInfo>(aliases: new[] { "--output-path", "-o" }, description: "Output folder", getDefaultValue: () => new DirectoryInfo(Environment.CurrentDirectory));
9494
var objectIdOpt = new Option<long>(aliases: new[] { "--objectid", "-i" }, () => 0, "Only dump the object with this signed 64-bit id (default: 0, dump all objects)");
95+
var typeOpt = new Option<string>(aliases: new[] { "--type", "-t" }, description: "Filter by object type (ClassID number or type name)");
9596

9697
var dOpt = new Option<FileInfo>(aliases: new[] { "--typetree-data", "-d" }, description: typeTreeDataDescription);
9798

@@ -102,16 +103,17 @@ public static async Task<int> Main(string[] args)
102103
sOpt,
103104
oOpt,
104105
objectIdOpt,
106+
typeOpt,
105107
dOpt,
106108
};
107109
dumpCommand.SetHandler(
108-
(FileInfo fi, DumpFormat f, bool s, DirectoryInfo o, long objectId, FileInfo d) =>
110+
(FileInfo fi, DumpFormat f, bool s, DirectoryInfo o, long objectId, string type, FileInfo d) =>
109111
{
110112
var ttResult = LoadTypeTreeDataFile(d);
111113
if (ttResult != 0) return Task.FromResult(ttResult);
112-
return Task.FromResult(HandleDump(fi, f, s, o, objectId));
114+
return Task.FromResult(HandleDump(fi, f, s, o, objectId, type));
113115
},
114-
pathArg, fOpt, sOpt, oOpt, objectIdOpt, dOpt);
116+
pathArg, fOpt, sOpt, oOpt, objectIdOpt, typeOpt, dOpt);
115117

116118
rootCommand.AddCommand(dumpCommand);
117119
}
@@ -274,14 +276,14 @@ static int HandleFindReferences(FileInfo databasePath, string outputFile, long?
274276
}
275277
}
276278

277-
static int HandleDump(FileInfo filename, DumpFormat format, bool skipLargeArrays, DirectoryInfo outputFolder, long objectId = 0)
279+
static int HandleDump(FileInfo filename, DumpFormat format, bool skipLargeArrays, DirectoryInfo outputFolder, long objectId = 0, string typeFilter = null)
278280
{
279281
switch (format)
280282
{
281283
case DumpFormat.Text:
282284
{
283285
var textDumper = new TextDumperTool();
284-
return textDumper.Dump(filename.FullName, outputFolder.FullName, skipLargeArrays, objectId);
286+
return textDumper.Dump(filename.FullName, outputFolder.FullName, skipLargeArrays, objectId, typeFilter);
285287
}
286288
}
287289

0 commit comments

Comments
 (0)