See More

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); } }