-
Notifications
You must be signed in to change notification settings - Fork 69
Expand file tree
/
Copy pathProgram.cs
More file actions
290 lines (238 loc) · 11.5 KB
/
Copy pathProgram.cs
File metadata and controls
290 lines (238 loc) · 11.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
using System;
using System.CommandLine;
using System.IO;
using System.Threading.Tasks;
using UnityDataTools.Analyzer;
using UnityDataTools.FileSystem;
using UnityDataTools.ReferenceFinder;
using UnityDataTools.TextDumper;
namespace UnityDataTools.UnityDataTool;
public enum OutputFormat
{
Text,
Json
}
public static class Program
{
public static async Task<int> Main(string[] args)
{
UnityFileSystem.Init();
var rootCommand = new RootCommand();
var typeTreeDataDescription = "Path to an external TypeTree data file to load before processing bundles";
{
var pathArg = new Argument<DirectoryInfo>("path", "The path to the directory containing the files to analyze").ExistingOnly();
var oOpt = new Option<string>(aliases: new[] { "--output-file", "-o" }, description: "Filename of the output database", getDefaultValue: () => "database.db");
var sOpt = new Option<bool>(aliases: new[] { "--skip-references", "-s" }, description: "Skip CRC and do not extract references");
var rOpt = new Option<bool>(aliases: new[] { "--extract-references", "-r" }) { IsHidden = true };
var pOpt = new Option<string>(aliases: new[] { "--search-pattern", "-p" }, description: "File search pattern", getDefaultValue: () => "*");
var vOpt = new Option<bool>(aliases: new[] { "--verbose", "-v" }, description: "Verbose output");
var recurseOpt = new Option<bool>(aliases: new[] { "--no-recurse" }, description: "Do not analyze contents of subdirectories inside path");
var dOpt = new Option<FileInfo>(aliases: new[] { "--typetree-data", "-d" }, description: typeTreeDataDescription);
var analyzeCommand = new Command("analyze", "Analyze AssetBundles or SerializedFiles.")
{
pathArg,
oOpt,
sOpt,
rOpt,
pOpt,
vOpt,
recurseOpt,
dOpt
};
analyzeCommand.AddAlias("analyse");
analyzeCommand.SetHandler(
(DirectoryInfo di, string o, bool s, bool r, string p, bool v, bool noRecurse, FileInfo d) =>
{
var ttResult = LoadTypeTreeDataFile(d);
if (ttResult != 0) return Task.FromResult(ttResult);
return Task.FromResult(HandleAnalyze(di, o, s, r, p, v, noRecurse));
},
pathArg, oOpt, sOpt, rOpt, pOpt, vOpt, recurseOpt, dOpt);
rootCommand.AddCommand(analyzeCommand);
}
{
var pathArg = new Argument<FileInfo>("databasePath", "The path to the database generated by the 'analyze' command").ExistingOnly();
var oOpt = new Option<string>(aliases: new[] { "--output-file", "-o" }, description: "Output file", getDefaultValue: () => "references.txt");
var iOpt = new Option<long?>(aliases: new[] { "--object-id", "-i" }, description: "Object id ('id' column in the database)");
var nOpt = new Option<string>(aliases: new[] { "--object-name", "-n" }, description: "Object name");
var tOpt = new Option<string>(aliases: new[] { "--object-type", "-t" }, description: "Optional object type when searching by name");
var aOpt = new Option<bool>(aliases: new[] { "--find-all", "-a" }, description: "Find all reference chains originating from the same asset (instead of only one), can be very slow");
var findRefsCommand = new Command("find-refs", "Find reference chains to specified object(s).")
{
pathArg,
oOpt,
aOpt,
nOpt,
tOpt,
iOpt,
};
findRefsCommand.SetHandler(
(FileInfo fi, string o, long? i, string n, string t, bool a) => Task.FromResult(HandleFindReferences(fi, o, i, n, t, a)),
pathArg, oOpt, iOpt, nOpt, tOpt, aOpt);
rootCommand.Add(findRefsCommand);
}
{
var pathArg = new Argument<FileInfo>("filename", "The path of the file to dump").ExistingOnly();
var fOpt = new Option<DumpFormat>(aliases: new[] { "--output-format", "-f" }, description: "Output format", getDefaultValue: () => DumpFormat.Text);
var sOpt = new Option<bool>(aliases: new[] { "--skip-large-arrays", "-s" }, description: "Do not dump large arrays of basic data types");
var oOpt = new Option<DirectoryInfo>(aliases: new[] { "--output-path", "-o" }, description: "Output folder", getDefaultValue: () => new DirectoryInfo(Environment.CurrentDirectory));
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)");
var dOpt = new Option<FileInfo>(aliases: new[] { "--typetree-data", "-d" }, description: typeTreeDataDescription);
var dumpCommand = new Command("dump", "Dump the contents of an AssetBundle or SerializedFile.")
{
pathArg,
fOpt,
sOpt,
oOpt,
objectIdOpt,
dOpt,
};
dumpCommand.SetHandler(
(FileInfo fi, DumpFormat f, bool s, DirectoryInfo o, long objectId, FileInfo d) =>
{
var ttResult = LoadTypeTreeDataFile(d);
if (ttResult != 0) return Task.FromResult(ttResult);
return Task.FromResult(HandleDump(fi, f, s, o, objectId));
},
pathArg, fOpt, sOpt, oOpt, objectIdOpt, dOpt);
rootCommand.AddCommand(dumpCommand);
}
{
var pathArg = new Argument<FileInfo>("filename", "The path of the archive file").ExistingOnly();
var oOpt = new Option<DirectoryInfo>(aliases: new[] { "--output-path", "-o" }, description: "Output directory of the extracted archive", getDefaultValue: () => new DirectoryInfo("archive"));
var extractArchiveCommand = new Command("extract", "Extract an AssetBundle or .data file.")
{
pathArg,
oOpt,
};
extractArchiveCommand.SetHandler(
(FileInfo fi, DirectoryInfo o) => Task.FromResult(Archive.HandleExtract(fi, o)),
pathArg, oOpt);
var listArchiveCommand = new Command("list", "List the contents of an AssetBundle or .data file.")
{
pathArg,
};
listArchiveCommand.SetHandler(
(FileInfo fi) => Task.FromResult(Archive.HandleList(fi)),
pathArg);
var archiveCommand = new Command("archive", "Inspect or extract the contents of a Unity archive (AssetBundle or web platform .data file).")
{
extractArchiveCommand,
listArchiveCommand,
};
rootCommand.AddCommand(archiveCommand);
}
{
var pathArg = new Argument<FileInfo>("filename", "The path of the SerializedFile").ExistingOnly();
var fOpt = new Option<OutputFormat>(aliases: new[] { "--format", "-f" }, description: "Output format", getDefaultValue: () => OutputFormat.Text);
var externalRefsCommand = new Command("externalrefs", "List external file references in a SerializedFile.")
{
pathArg,
fOpt,
};
externalRefsCommand.SetHandler(
(FileInfo fi, OutputFormat f) => Task.FromResult(SerializedFileCommands.HandleExternalRefs(fi, f)),
pathArg, fOpt);
var objectListCommand = new Command("objectlist", "List all objects in a SerializedFile.")
{
pathArg,
fOpt,
};
objectListCommand.SetHandler(
(FileInfo fi, OutputFormat f) => Task.FromResult(SerializedFileCommands.HandleObjectList(fi, f)),
pathArg, fOpt);
var headerCommand = new Command("header", "Show SerializedFile header information.")
{
pathArg,
fOpt,
};
headerCommand.SetHandler(
(FileInfo fi, OutputFormat f) => Task.FromResult(SerializedFileCommands.HandleHeader(fi, f)),
pathArg, fOpt);
var metadataCommand = new Command("metadata", "Show information from the metadata section of the SerializedFile (use `-f Json` for detailed information).")
{
pathArg,
fOpt,
};
metadataCommand.SetHandler(
(FileInfo fi, OutputFormat f) => Task.FromResult(SerializedFileCommands.HandleMetadata(fi, f)),
pathArg, fOpt);
var serializedFileCommand = new Command("serialized-file", "Inspect a SerializedFile (scene, assets, etc.).")
{
externalRefsCommand,
objectListCommand,
headerCommand,
metadataCommand,
};
serializedFileCommand.AddAlias("sf");
rootCommand.AddCommand(serializedFileCommand);
}
var r = await rootCommand.InvokeAsync(args);
UnityFileSystem.Cleanup();
return r;
}
enum DumpFormat
{
Text,
}
static int LoadTypeTreeDataFile(FileInfo typeTreeDataFile)
{
if (typeTreeDataFile == null)
return 0;
try
{
UnityFileSystem.AddTypeTreeSourceFromFile(typeTreeDataFile.FullName);
}
catch (EntryPointNotFoundException)
{
Console.Error.WriteLine("Error: The version of UnityFileSystemApi does not support external TypeTree data files. Please use a version from Unity 6.5 or newer.");
return 1;
}
return 0;
}
static int HandleAnalyze(
DirectoryInfo path,
string outputFile,
bool skipReferences,
bool extractReferences,
string searchPattern,
bool verbose,
bool noRecurse)
{
var analyzer = new AnalyzerTool();
if (extractReferences)
{
Console.WriteLine("WARNING: --extract-references, -r option is deprecated (references are now extracted by default)");
}
return analyzer.Analyze(path.FullName, outputFile, searchPattern, skipReferences, verbose, noRecurse);
}
static int HandleFindReferences(FileInfo databasePath, string outputFile, long? objectId, string objectName, string objectType, bool findAll)
{
var finder = new ReferenceFinderTool();
if ((objectId != null && objectName != null) || (objectId == null && objectName == null))
{
Console.Error.WriteLine("A value must be provided for either --object-id or --object-name.");
return 1;
}
if (objectId != null)
{
return finder.FindReferences(objectId.Value, databasePath.FullName, outputFile, findAll);
}
else
{
return finder.FindReferences(objectName, objectType, databasePath.FullName, outputFile, findAll);
}
}
static int HandleDump(FileInfo filename, DumpFormat format, bool skipLargeArrays, DirectoryInfo outputFolder, long objectId = 0)
{
switch (format)
{
case DumpFormat.Text:
{
var textDumper = new TextDumperTool();
return textDumper.Dump(filename.FullName, outputFolder.FullName, skipLargeArrays, objectId);
}
}
return 1;
}
}