using System;
using System.CommandLine;
using System.IO;
using System.Threading.Tasks;
using UnityDataTools.Analyzer;
using UnityDataTools.Analyzer.SQLite.Handlers;
using UnityDataTools.ReferenceFinder;
using UnityDataTools.TextDumper;
using UnityDataTools.FileSystem;
namespace UnityDataTools.UnityDataTool;
public static class Program
{
public static async Task Main(string[] args)
{
UnityFileSystem.Init();
var rootCommand = new RootCommand();
{
var pathArg = new Argument("path", "The path to the directory containing the files to analyze").ExistingOnly();
var oOpt = new Option(aliases: new[] { "--output-file", "-o" }, description: "Filename of the output database", getDefaultValue: () => "database.db");
var rOpt = new Option(aliases: new[] { "--extract-references", "-r" }, description: "Extract all object references and add them in the 'refs' table");
var pOpt = new Option(aliases: new[] { "--search-pattern", "-p" }, description: "File search pattern", getDefaultValue: () => "*");
var analyzeCommand = new Command("analyze", "Analyze AssetBundles or SerializedFiles.")
{
pathArg,
oOpt,
rOpt,
pOpt,
};
analyzeCommand.AddAlias("analyse");
analyzeCommand.SetHandler(
(DirectoryInfo di, string o, bool r, string p) => Task.FromResult(HandleAnalyze(di, o, r, p)),
pathArg, oOpt, rOpt, pOpt);
rootCommand.AddCommand(analyzeCommand);
}
{
var pathArg = new Argument("databasePath", "The path to the database generated by the 'analyze' command using --extract-references").ExistingOnly();
var oOpt = new Option(aliases: new[] { "--output-file", "-o" }, description: "Output file", getDefaultValue: () => "references.txt");
var iOpt = new Option(aliases: new[] { "--object-id", "-i" }, description: "Object id ('id' column in the database)");
var nOpt = new Option(aliases: new[] { "--object-name", "-n" }, description: "Object name");
var tOpt = new Option(aliases: new[] { "--object-type", "-t" }, description: "Optional object type when searching by name");
var aOpt = new Option(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("filename", "The path of the file to dump").ExistingOnly();
var fOpt = new Option(aliases: new[] { "--output-format", "-f" }, description: "Output format", getDefaultValue: () => DumpFormat.Text);
var sOpt = new Option(aliases: new[] { "--skip-large-arrays", "-s" }, description: "Do not dump large arrays of basic data types");
var oOpt = new Option(aliases: new[] { "--output-path", "-o"}, description: "Output folder", getDefaultValue: () => new DirectoryInfo(Environment.CurrentDirectory));
var dumpCommand = new Command("dump", "Dump the content of an AssetBundle or SerializedFile.")
{
pathArg,
fOpt,
sOpt,
oOpt,
};
dumpCommand.SetHandler(
(FileInfo fi, DumpFormat f, bool s, DirectoryInfo o) => Task.FromResult(HandleDump(fi, f, s, o)),
pathArg, fOpt, sOpt, oOpt);
rootCommand.AddCommand(dumpCommand);
}
{
var pathArg = new Argument("filename", "The path of the archive file").ExistingOnly();
var oOpt = new Option(aliases: new[] { "--output-path", "-o" }, description: "Output directory of the extracted archive", getDefaultValue: () => new DirectoryInfo("archive"));
var extractArchiveCommand = new Command("extract", "Extract the archive.")
{
pathArg,
oOpt,
};
extractArchiveCommand.SetHandler(
(FileInfo fi, DirectoryInfo o) => Task.FromResult(HandleExtractArchive(fi, o)),
pathArg, oOpt);
var listArchiveCommand = new Command("list", "List the content of an archive.")
{
pathArg,
};
listArchiveCommand.SetHandler(
(FileInfo fi) => Task.FromResult(HandleListArchive(fi)),
pathArg);
var archiveCommand = new Command("archive", "Unity Archive (AssetBundle) functions.")
{
extractArchiveCommand,
listArchiveCommand,
};
rootCommand.AddCommand(archiveCommand);
}
var r = await rootCommand.InvokeAsync(args);
UnityFileSystem.Cleanup();
return r;
}
enum DumpFormat
{
Text,
}
static int HandleAnalyze(DirectoryInfo path, string outputFile, bool extractReferences, string searchPattern)
{
var analyzer = new AnalyzerTool();
return analyzer.Analyze(path.FullName, outputFile, searchPattern, extractReferences);
}
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)
{
switch (format)
{
case DumpFormat.Text:
{
var textDumper = new TextDumperTool();
return textDumper.Dump(filename.FullName, outputFolder.FullName, skipLargeArrays);
}
}
return 1;
}
static int HandleExtractArchive(FileInfo filename, DirectoryInfo outputFolder)
{
try
{
using var archive = UnityFileSystem.MountArchive(filename.FullName, "/");
foreach (var node in archive.Nodes)
{
Console.WriteLine($"Extracting {node.Path}...");
CopyFile("/" + node.Path, Path.Combine(outputFolder.FullName, node.Path));
}
}
catch (NotSupportedException)
{
Console.Error.WriteLine("Error opening archive!");
return 1;
}
return 0;
}
static int HandleListArchive(FileInfo filename)
{
try
{
using var archive = UnityFileSystem.MountArchive(filename.FullName, "/");
foreach (var node in archive.Nodes)
{
Console.WriteLine($"{node.Path}");
Console.WriteLine($" Size: {node.Size}");
Console.WriteLine($" Flags: {node.Flags}");
Console.WriteLine();
}
}
catch (NotSupportedException)
{
Console.Error.WriteLine("Error opening archive!");
return 1;
}
return 0;
}
static void CopyFile(string source, string dest)
{
using var sourceFile = UnityFileSystem.OpenFile(source);
// Create the containing directory if it doesn't exist.
Directory.CreateDirectory(Path.GetDirectoryName(dest));
using var destFile = new FileStream(dest, FileMode.Create);
const int blockSize = 256 * 1024;
var buffer = new byte[blockSize];
long actualSize;
do
{
actualSize = sourceFile.Read(blockSize, buffer);
destFile.Write(buffer, 0, (int)actualSize);
}
while (actualSize == blockSize);
}
}