Skip to content

Commit 8925e43

Browse files
committed
Add separate parsers
This refactors the code to have separate parsers for Asset Bundles and Addressables Build Layout files. The parser implements a CanParse and Parse method to handle applicability. Currently there's no overlap so we won't be double-parsing, but it's possible in the future.
1 parent 6206b19 commit 8925e43

11 files changed

Lines changed: 1219 additions & 1164 deletions

Analyzer/Analyzer.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<TargetFramework>net9.0</TargetFramework>

Analyzer/AnalyzerTool.cs

Lines changed: 47 additions & 181 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
using Newtonsoft.Json;
1+
using Analyzer.SQLite.Parsers;
2+
using Analyzer.SQLite.Writers;
3+
using Newtonsoft.Json;
24
using Newtonsoft.Json.Linq;
35
using System;
46
using System.Collections.Generic;
57
using System.Diagnostics;
68
using System.IO;
79
using UnityDataTools.Analyzer.Build;
8-
using UnityDataTools.Analyzer.SQLite;
10+
using UnityDataTools.Analyzer.SQLite.Handlers;
911
using UnityDataTools.FileSystem;
1012

1113
namespace UnityDataTools.Analyzer;
@@ -14,6 +16,12 @@ public class AnalyzerTool
1416
{
1517
bool m_Verbose = false;
1618

19+
public List<ISQLiteFileParser> parsers = new List<ISQLiteFileParser>()
20+
{
21+
new AddressablesBuildLayoutParser(),
22+
new SerializedFileParser(),
23+
};
24+
1725
public int Analyze(
1826
string path,
1927
string databaseName,
@@ -24,11 +32,19 @@ public int Analyze(
2432
{
2533
m_Verbose = verbose;
2634

27-
using SQLiteWriter writer = new (databaseName, skipReferences);
35+
// TODO: skipReferences needs to be passed into AssetBundleWriter
36+
using SQLiteWriter writer = new (databaseName);
2837

2938
try
3039
{
3140
writer.Begin();
41+
foreach (var parser in parsers)
42+
{
43+
parser.Verbose = verbose;
44+
parser.SkipReferences = skipReferences;
45+
parser.Init(writer.Connection);
46+
47+
}
3248
}
3349
catch (Exception e)
3450
{
@@ -47,13 +63,31 @@ public int Analyze(
4763
int i = 1;
4864
foreach (var file in files)
4965
{
50-
if (Path.GetExtension(file) == ".json" && IsAddressablesBuildReport(file))
66+
bool foundParser = false;
67+
foreach(var parser in parsers)
5168
{
52-
ProcessAddressablesBuild(file, writer, i, files.Length);
53-
++i;
54-
continue;
69+
if (parser.CanParse(file))
70+
{
71+
foundParser = true;
72+
Console.Error.WriteLine(file);
73+
try
74+
{
75+
parser.Parse(file);
76+
ReportProgress(Path.GetRelativePath(path, file), i, files.Length);
77+
}
78+
catch (Exception e)
79+
{
80+
EraseProgressLine();
81+
Console.Error.WriteLine();
82+
Console.Error.WriteLine($"Error processing file: {file}");
83+
Console.WriteLine($"{e.GetType()}: {e.Message}");
84+
if (m_Verbose)
85+
Console.WriteLine(e.StackTrace);
86+
}
87+
++i;
88+
}
5589
}
56-
if (ShouldIgnoreFile(file))
90+
if (!foundParser)
5791
{
5892
var relativePath = Path.GetRelativePath(path, file);
5993

@@ -63,152 +97,25 @@ public int Analyze(
6397
Console.WriteLine($"Ignoring {relativePath}");
6498
}
6599
++i;
66-
continue;
67100
}
68-
69-
ProcessFile(file, path, writer, i, files.Length);
70-
++i;
71101
}
72102

73103
Console.WriteLine();
74104
Console.WriteLine("Finalizing database...");
75105

76106
writer.End();
107+
foreach (var parser in parsers)
108+
{
109+
parser.Dispose();
110+
}
77111

78-
timer.Stop();
112+
timer.Stop();
79113
Console.WriteLine();
80114
Console.WriteLine($"Total time: {(timer.Elapsed.TotalMilliseconds / 1000.0):F3} s");
81115

82116
return 0;
83117
}
84118

85-
bool ShouldIgnoreFile(string file)
86-
{
87-
// Unfortunately there is no standard extension for AssetBundles, and SerializedFiles often have no extension at all.
88-
// Also there is also no distinctive signature at the start of a SerializedFile to immediately recognize it based on its first bytes.
89-
// This makes it difficult to use the "--search-pattern" argument to only pick those files.
90-
91-
// Hence to reduce noise in UnityDataTool output we filter out files that we have a high confidence are
92-
// NOT SerializedFiles or Unity Archives.
93-
94-
string fileName = Path.GetFileName(file);
95-
string extension = Path.GetExtension(file);
96-
97-
return IgnoredFileNames.Contains(fileName) || IgnoredExtensions.Contains(extension);
98-
}
99-
100-
// These lists are based on expected output files in Player, AssetBundle, Addressables and ECS builds.
101-
// However this is by no means exhaustive.
102-
private static readonly HashSet<string> IgnoredFileNames = new()
103-
{
104-
".DS_Store", "boot.config", "archive_dependencies.bin", "scene_info.bin", "app.info", "link.xml",
105-
"catalog.bin", "catalog.hash"
106-
};
107-
108-
private static readonly HashSet<string> IgnoredExtensions = new()
109-
{
110-
".txt", ".resS", ".resource", ".json", ".dll", ".pdb", ".exe", ".manifest", ".entities", ".entityheader"
111-
};
112-
113-
void ProcessFile(string file, string rootDirectory, SQLiteWriter writer, int fileIndex, int cntFiles)
114-
{
115-
try
116-
{
117-
UnityArchive archive = null;
118-
119-
try
120-
{
121-
archive = UnityFileSystem.MountArchive(file, "archive:" + Path.DirectorySeparatorChar);
122-
}
123-
catch (NotSupportedException)
124-
{
125-
// It wasn't an AssetBundle, try to open the file as a SerializedFile.
126-
127-
var relativePath = Path.GetRelativePath(rootDirectory, file);
128-
writer.WriteSerializedFile(relativePath, file, Path.GetDirectoryName(file));
129-
130-
ReportProgress(relativePath, fileIndex, cntFiles);
131-
}
132-
133-
if (archive != null)
134-
{
135-
try
136-
{
137-
var assetBundleName = Path.GetRelativePath(rootDirectory, file);
138-
139-
writer.BeginAssetBundle(assetBundleName, new FileInfo(file).Length);
140-
ReportProgress(assetBundleName, fileIndex, cntFiles);
141-
142-
foreach (var node in archive.Nodes)
143-
{
144-
if (node.Flags.HasFlag(ArchiveNodeFlags.SerializedFile))
145-
{
146-
try
147-
{
148-
writer.WriteSerializedFile(node.Path, "archive:/" + node.Path, Path.GetDirectoryName(file));
149-
}
150-
catch (Exception e)
151-
{
152-
EraseProgressLine();
153-
Console.Error.WriteLine($"Error processing {node.Path} in archive {file}");
154-
Console.Error.WriteLine(e);
155-
Console.WriteLine();
156-
}
157-
}
158-
}
159-
}
160-
finally
161-
{
162-
writer.EndAssetBundle();
163-
archive.Dispose();
164-
}
165-
}
166-
EraseProgressLine();
167-
}
168-
catch (NotSupportedException)
169-
{
170-
EraseProgressLine();
171-
Console.Error.WriteLine();
172-
//A "failed to load" error will already be logged by the UnityFileSystem library
173-
}
174-
catch (Exception e)
175-
{
176-
EraseProgressLine();
177-
Console.Error.WriteLine();
178-
Console.Error.WriteLine($"Error processing file: {file}");
179-
Console.WriteLine($"{e.GetType()}: {e.Message}");
180-
if (m_Verbose)
181-
Console.WriteLine(e.StackTrace);
182-
}
183-
}
184-
185-
186-
187-
void ProcessAddressablesBuild(string file, SQLiteWriter writer, int fileIndex, int cntFiles)
188-
{
189-
try
190-
{
191-
Console.Error.WriteLine(file);
192-
using (StreamReader reader = File.OpenText(file))
193-
{
194-
JsonSerializer serializer = new JsonSerializer();
195-
BuildLayout buildLayout = (BuildLayout)serializer.Deserialize(reader, typeof(BuildLayout));
196-
writer.WriteAddressablesBuild(file, buildLayout);
197-
ReportProgress(file, fileIndex, cntFiles);
198-
}
199-
}
200-
catch (Exception e)
201-
{
202-
EraseProgressLine();
203-
Console.Error.WriteLine();
204-
Console.Error.WriteLine($"Error processing file: {file}");
205-
Console.WriteLine($"{e.GetType()}: {e.Message}");
206-
if (m_Verbose)
207-
Console.WriteLine(e.StackTrace);
208-
}
209-
210-
}
211-
212119
int m_LastProgressMessageLength = 0;
213120

214121
void ReportProgress(string relativePath, int fileIndex, int cntFiles)
@@ -235,45 +142,4 @@ void EraseProgressLine()
235142
else
236143
Console.WriteLine();
237144
}
238-
239-
bool IsAddressablesBuildReport(string filename)
240-
{
241-
// Read the first line of the JSON file and check if it contains BuildResultHash
242-
string firstLine = "";
243-
try
244-
{
245-
using (StreamReader reader = new StreamReader(filename))
246-
{
247-
firstLine = reader.ReadLine();
248-
if (firstLine != null)
249-
{
250-
// Remove trailing comma if present and add closing brace to make it valid JSON
251-
if (firstLine.TrimEnd().EndsWith(","))
252-
{
253-
firstLine = firstLine.TrimEnd().TrimEnd(',') + "}";
254-
}
255-
256-
using (JsonTextReader jsonReader = new JsonTextReader(new StringReader(firstLine)))
257-
{
258-
JsonSerializer serializer = new JsonSerializer();
259-
var jsonObject = serializer.Deserialize<JObject>(jsonReader);
260-
261-
// If the file has BuildResultHash, process it as an Addressables build
262-
if (jsonObject != null && jsonObject["BuildResultHash"] != null)
263-
{
264-
return true;
265-
}
266-
}
267-
}
268-
}
269-
}
270-
catch (Exception e)
271-
{
272-
if (m_Verbose)
273-
{
274-
Console.Error.WriteLine($"Error reading JSON file {filename}: {e.Message}");
275-
}
276-
}
277-
return false;
278-
}
279145
}

Analyzer/IWriter.cs

Lines changed: 0 additions & 15 deletions
This file was deleted.

0 commit comments

Comments
 (0)