Skip to content

Commit 99ce157

Browse files
[#70] Fix analyze CRC for cah:/ ContentDirectory resources; add --skip-crc (#73)
Fixes for CRC calculation Refactor and improve maintenance of PPtrAndCrcProcessor
1 parent 0c36a90 commit 99ce157

12 files changed

Lines changed: 331 additions & 122 deletions

File tree

‎.gitignore‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,5 @@ UnityFileSystemTestData/UserSettings/
4040
UnityFileSystemTestData/Packages/
4141
*.db
4242
*.csv
43+
44+
*.stackdump

‎Analyzer/AnalyzerTool.cs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public int Analyze(
2626
string databaseName,
2727
string searchPattern,
2828
bool skipReferences,
29+
bool skipCrc,
2930
bool verbose,
3031
bool noRecursion)
3132
{
@@ -40,6 +41,7 @@ public int Analyze(
4041
{
4142
parser.Verbose = verbose;
4243
parser.SkipReferences = skipReferences;
44+
parser.SkipCrc = skipCrc;
4345
parser.Init(writer.Connection);
4446

4547
}

‎Analyzer/PPtrAndCrcProcessor.cs‎

Lines changed: 228 additions & 83 deletions
Large diffs are not rendered by default.

‎Analyzer/SQLite/Handlers/ISQLiteHandler.cs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,5 @@ public interface ISQLiteFileParser : IDisposable
2929
void Parse(string filename);
3030
public bool Verbose { get; set; }
3131
public bool SkipReferences { get; set; }
32+
public bool SkipCrc { get; set; }
3233
}

‎Analyzer/SQLite/Parsers/AddressablesBuildLayoutParser.cs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public class AddressablesBuildLayoutParser : ISQLiteFileParser
1515

1616
public bool Verbose { get; set; }
1717
public bool SkipReferences { get; set; }
18+
public bool SkipCrc { get; set; }
1819

1920
public void Dispose()
2021
{

‎Analyzer/SQLite/Parsers/SerializedFileParser.cs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public class SerializedFileParser : ISQLiteFileParser
1515

1616
public bool Verbose { get; set; }
1717
public bool SkipReferences { get; set; }
18+
public bool SkipCrc { get; set; }
1819

1920
public bool CanParse(string filename)
2021
{
@@ -36,7 +37,7 @@ public void Dispose()
3637

3738
public void Init(SqliteConnection db)
3839
{
39-
m_Writer = new SerializedFileSQLiteWriter(db, SkipReferences);
40+
m_Writer = new SerializedFileSQLiteWriter(db, SkipReferences, SkipCrc);
4041
}
4142

4243
public void Parse(string filename)

‎Analyzer/SQLite/Writers/SerializedFileSQLiteWriter.cs‎

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public class SerializedFileSQLiteWriter : IDisposable
1919
private int m_NextAssetBundleId = 0;
2020

2121
private bool m_SkipReferences;
22+
private bool m_SkipCrc;
2223

2324
private IdProvider<string> m_SerializedFileIdProvider = new();
2425
private ObjectIdProvider m_ObjectIdProvider = new();
@@ -54,11 +55,12 @@ public class SerializedFileSQLiteWriter : IDisposable
5455
private SqliteConnection m_Database;
5556
private SqliteCommand m_LastId = new SqliteCommand();
5657
private SqliteTransaction m_CurrentTransaction = null;
57-
public SerializedFileSQLiteWriter(SqliteConnection database, bool skipReferences)
58+
public SerializedFileSQLiteWriter(SqliteConnection database, bool skipReferences, bool skipCrc)
5859
{
5960
m_Initialized = false;
6061
m_Database = database;
6162
m_SkipReferences = skipReferences;
63+
m_SkipCrc = skipCrc;
6264
}
6365

6466
public void Init()
@@ -116,7 +118,7 @@ public void WriteSerializedFile(string relativePath, string fullPath, string con
116118
{
117119
using var sf = UnityFileSystem.OpenSerializedFile(fullPath);
118120
using var reader = new UnityFileReader(fullPath, 64 * 1024 * 1024);
119-
using var pptrReader = new PPtrAndCrcProcessor(sf, reader, containingFolder, AddReference);
121+
using var pptrReader = new PPtrAndCrcProcessor(sf, reader, containingFolder, m_SkipCrc, AddReference);
120122
int serializedFileId = m_SerializedFileIdProvider.GetId(Path.GetFileName(fullPath).ToLower());
121123
int sceneId = -1;
122124

@@ -228,7 +230,10 @@ public void WriteSerializedFile(string relativePath, string fullPath, string con
228230
m_AddObjectCommand.SetValue("game_object", "");
229231
}
230232

231-
if (!m_SkipReferences)
233+
// The walk both extracts references and accumulates the CRC, so it is needed
234+
// unless both are disabled. When CRC is on but references are off, the walk
235+
// still resolves referenced object ids (AddReference skips the insert).
236+
if (!m_SkipReferences || !m_SkipCrc)
232237
{
233238
crc32 = pptrReader.Process(currentObjectId, offset, root);
234239
}
@@ -264,15 +269,23 @@ public void WriteSerializedFile(string relativePath, string fullPath, string con
264269
}
265270
}
266271

272+
// Callback from PPtrAndCrcProcessor for each reference discovered in the SerializedFile
267273
private int AddReference(long objectId, int fileId, long pathId, string propertyPath, string propertyType)
268274
{
275+
// Always resolve the id so the CRC stays stable; only persist the row when references
276+
// are being extracted.
269277
var referencedObjectId = m_ObjectIdProvider.GetId((m_LocalToDbFileId[fileId], pathId));
270-
m_AddReferenceCommand.SetTransaction(m_CurrentTransaction);
271-
m_AddReferenceCommand.SetValue("object", objectId);
272-
m_AddReferenceCommand.SetValue("referenced_object", referencedObjectId);
273-
m_AddReferenceCommand.SetValue("property_path", propertyPath);
274-
m_AddReferenceCommand.SetValue("property_type", propertyType);
275-
m_AddReferenceCommand.ExecuteNonQuery();
278+
279+
if (!m_SkipReferences)
280+
{
281+
m_AddReferenceCommand.SetTransaction(m_CurrentTransaction);
282+
m_AddReferenceCommand.SetValue("object", objectId);
283+
m_AddReferenceCommand.SetValue("referenced_object", referencedObjectId);
284+
m_AddReferenceCommand.SetValue("property_path", propertyPath);
285+
m_AddReferenceCommand.SetValue("property_type", propertyType);
286+
m_AddReferenceCommand.ExecuteNonQuery();
287+
}
288+
276289
return referencedObjectId;
277290
}
278291

‎Documentation/analyzer.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ case, Unity will include the asset in all the AssetBundles with a reference to i
4747
view_potential_duplicates provides the number of instances and the total size of the potentially
4848
duplicated assets. It also lists all the AssetBundles where the asset was found.
4949

50-
If the skipReferences option is used, there will be a lot of false positives in that view. Otherwise,
50+
If the `--skip-crc` option is used, there will be a lot of false positives in that view. Otherwise,
5151
it should be very accurate because CRCs are used to determine if objects are identical.
5252

5353
## asset_view (AssetBundleProcessor)

‎Documentation/command-analyze.md‎

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ UnityDataTool analyze <path> [options]
1313
| `<path>` | Path to folder containing files to analyze | *(required)* |
1414
| `-o, --output-file <file>` | Output database filename | `database.db` |
1515
| `-p, --search-pattern <pattern>` | File search pattern (`*` and `?` supported) | `*` |
16-
| `-s, --skip-references` | Skip CRC and reference extraction (faster, smaller DB) | `false` |
16+
| `-s, --skip-references` | Do not extract references (smaller DB, no `refs` table). CRC is still computed. | `false` |
17+
| `--skip-crc` | Skip the CRC32 checksum calculation (faster; `objects.crc32` will be 0) | `false` |
1718
| `-v, --verbose` | Show more information during analysis | `false` |
1819
| `--no-recurse` | Do not recurse into sub-directories | `false` |
1920
| `-d, --typetree-data <file>` | Load an external TypeTree data file before processing (Unity 6.5+) | — |
@@ -30,9 +31,9 @@ Analyze only `.bundle` files and specify a custom database name:
3031
UnityDataTool analyze /path/to/asset/bundles -o my_database.db -p "*.bundle"
3132
```
3233

33-
Fast analysis (skip reference tracking):
34+
Fastest analysis (skip both reference extraction and CRC):
3435
```bash
35-
UnityDataTool analyze /path/to/bundles -s
36+
UnityDataTool analyze /path/to/bundles --skip-references --skip-crc
3637
```
3738

3839
See also [Analyze Examples](../../Documentation/analyze-examples.md).
@@ -121,23 +122,27 @@ See [Comparing Builds](../../Documentation/comparing-builds.md) for strategies t
121122

122123
### Slow Analyze times, large output database
123124

124-
Consider using the `--skip-references` argument.
125+
Two independent flags reduce analyze time and database size:
125126

126-
A real life analyze of a big Addressables build shows how large a difference this can make:
127+
* `--skip-crc` skips the CRC32 calculation. This is usually the largest time saver, because computing a CRC requires reading the full content of every object, including large texture, mesh and audio data in companion `.resS`/`.resource` files.
128+
* `--skip-references` skips reference extraction, which is the largest contributor to database size (the `refs` table). The references are not needed for core asset inventory and size information.
127129

128-
* 208 seconds and producted a 500MB database (not specifying --skip-reference)
129-
* 9 seconds and produced a 68 MB file (with --skip-reference)
130+
For the fastest, smallest result, combine them.
130131

131-
The references are not needed for core asset inventory and size information.
132+
A real life analyze of a big Addressables build, skipping both references and CRC, shows how large a difference this can make:
132133

133-
Note: When specifying `--skip-reference` some functionality is lost:
134+
* 208 seconds and produced a 500MB database (default)
135+
* 9 seconds and produced a 68 MB file (with `--skip-references --skip-crc`)
136+
137+
When `--skip-references` is used, some functionality is lost:
134138

135139
* the `find-refs` command will not work
136140
* `view_material_shader_refs` and `view_material_texture_refs` will be empty
141+
* `script_object_view` will be empty
137142
* Queries that look at the relationship between objects will not work. For example the refs table is required to link between a `MonoBehaviour` and its `MonoScript`.
138-
* The `objects.crc32` column will be NULL/0 for all objects. This means:
139-
* No detection of identical objects by content hash (See [Comparing Builds](../../Documentation/comparing-builds.md))
140-
* The `view_potential_duplicates` view relies partially on CRC32 to distinguish true duplicates
141143

142-
Future work: The refs table contains a lot of repeated strings and could be made smaller and more efficient. It might also be prudent to control the CRC32 calculation using an independent flag.
144+
When `--skip-crc` is used, the `objects.crc32` column will be 0 for all objects. This means:
145+
146+
* No detection of identical objects by content hash (See [Comparing Builds](../../Documentation/comparing-builds.md))
147+
* The `view_potential_duplicates` view relies partially on CRC32 to distinguish true duplicates
143148

‎UnityDataTool/Program.cs‎

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.CommandLine;
3+
using System.CommandLine.Invocation;
34
using System.IO;
45
using System.Threading.Tasks;
56
using UnityDataTools.Analyzer;
@@ -41,7 +42,8 @@ static Command BuildAnalyzeCommand()
4142
{
4243
var pathArg = new Argument<DirectoryInfo>("path", "The path to the directory containing the files to analyze").ExistingOnly();
4344
var oOpt = new Option<string>(aliases: new[] { "--output-file", "-o" }, description: "Filename of the output database", getDefaultValue: () => "database.db");
44-
var sOpt = new Option<bool>(aliases: new[] { "--skip-references", "-s" }, description: "Skip CRC and do not extract references");
45+
var sOpt = new Option<bool>(aliases: new[] { "--skip-references", "-s" }, description: "Do not extract references (CRC is still computed unless --skip-crc is also given)");
46+
var scOpt = new Option<bool>(aliases: new[] { "--skip-crc" }, description: "Skip CRC checksum calculation");
4547
var rOpt = new Option<bool>(aliases: new[] { "--extract-references", "-r" }) { IsHidden = true };
4648
var pOpt = new Option<string>(aliases: new[] { "--search-pattern", "-p" }, description: "File search pattern", getDefaultValue: () => "*");
4749
var vOpt = new Option<bool>(aliases: new[] { "--verbose", "-v" }, description: "Verbose output");
@@ -53,6 +55,7 @@ static Command BuildAnalyzeCommand()
5355
pathArg,
5456
oOpt,
5557
sOpt,
58+
scOpt,
5659
rOpt,
5760
pOpt,
5861
vOpt,
@@ -61,14 +64,28 @@ static Command BuildAnalyzeCommand()
6164
};
6265

6366
analyzeCommand.AddAlias("analyse");
64-
analyzeCommand.SetHandler(
65-
(DirectoryInfo di, string o, bool s, bool r, string p, bool v, bool noRecurse, FileInfo d) =>
67+
// Bound via InvocationContext because the option count exceeds the strongly-typed
68+
// SetHandler overloads.
69+
analyzeCommand.SetHandler((InvocationContext context) =>
70+
{
71+
var d = context.ParseResult.GetValueForOption(dOpt);
72+
var ttResult = LoadTypeTreeDataFile(d);
73+
if (ttResult != 0)
6674
{
67-
var ttResult = LoadTypeTreeDataFile(d);
68-
if (ttResult != 0) return Task.FromResult(ttResult);
69-
return Task.FromResult(HandleAnalyze(di, o, s, r, p, v, noRecurse));
70-
},
71-
pathArg, oOpt, sOpt, rOpt, pOpt, vOpt, recurseOpt, dOpt);
75+
context.ExitCode = ttResult;
76+
return;
77+
}
78+
79+
context.ExitCode = HandleAnalyze(
80+
context.ParseResult.GetValueForArgument(pathArg),
81+
context.ParseResult.GetValueForOption(oOpt),
82+
context.ParseResult.GetValueForOption(sOpt),
83+
context.ParseResult.GetValueForOption(scOpt),
84+
context.ParseResult.GetValueForOption(rOpt),
85+
context.ParseResult.GetValueForOption(pOpt),
86+
context.ParseResult.GetValueForOption(vOpt),
87+
context.ParseResult.GetValueForOption(recurseOpt));
88+
});
7289

7390
return analyzeCommand;
7491
}
@@ -293,6 +310,7 @@ static int HandleAnalyze(
293310
DirectoryInfo path,
294311
string outputFile,
295312
bool skipReferences,
313+
bool skipCrc,
296314
bool extractReferences,
297315
string searchPattern,
298316
bool verbose,
@@ -305,7 +323,7 @@ static int HandleAnalyze(
305323
Console.WriteLine("WARNING: --extract-references, -r option is deprecated (references are now extracted by default)");
306324
}
307325

308-
return analyzer.Analyze(path.FullName, outputFile, searchPattern, skipReferences, verbose, noRecurse);
326+
return analyzer.Analyze(path.FullName, outputFile, searchPattern, skipReferences, skipCrc, verbose, noRecurse);
309327
}
310328

311329
static int HandleFindReferences(FileInfo databasePath, string outputFile, long? objectId, string objectName, string objectType, bool findAll)

0 commit comments

Comments
 (0)