using System; using System.Collections.Generic; using System.IO; using System.Reflection; using Oni.Akira; using Oni.Collections; using Oni.Metadata; using Oni.Sound; using Oni.Xml; namespace Oni { internal class Program { private static readonly InstanceFileManager fileManager = new InstanceFileManager(); private static int Main(string[] args) { if (args.Length == 0) { Help(args); Console.WriteLine("Press any key to continue"); Console.ReadKey(); return 0; } if (args[0] == "-cdump") { InstanceMetadata.DumpCStructs(Console.Out); return 0; } Dae.IO.DaeReader.CommandLineArgs = args; if (args[0] == "-silent") { Console.SetOut(new StreamWriter(Stream.Null)); Console.SetError(new StreamWriter(Stream.Null)); var newArgs = new string[args.Length - 1]; Array.Copy(args, 1, newArgs, 0, newArgs.Length); args = newArgs; } if (args[0] == "-noexcept") { var newArgs = new string[args.Length - 1]; Array.Copy(args, 1, newArgs, 0, newArgs.Length); args = newArgs; return Execute(args); } args = AddSearchPaths(args); try { return Execute(args); } catch (Exception ex) { Console.Error.WriteLine(ex.ToString()); return 1; } } private static int Execute(string[] args) { if (args[0].StartsWith("-export:", StringComparison.Ordinal)) return Unpack(args); switch (args[0]) { case "-help": return Help(args); case "-version": return PrintVersion(); case "-export": return Unpack(args); case "pack": case "-import": case "-import:nosep": case "-import:sep": case "-import:ppc": case "-import:pc": return Pack(args); case "-copy": return Copy(args); case "-move": case "-move:overwrite": case "-move:delete": return Move(args); case "-list": return List(args); case "-deps": return GetDependencies(args); case "extract": case "-extract:xml": return ExportXml(args); case "-extract:tga": case "-extract:dds": case "-extract:png": case "-extract:jpg": case "-extract:bmp": case "-extract:tif": return ExportTextures(args); case "-extract:wav": case "-extract:aif": case "-extract:pcm": return ExportSounds(args); case "-extract:obj": case "-extract:dae": return ExportGeometry(args); case "-extract:txt": return ExportSubtitles(args); case "-create:akev": return CreateAkira(args); case "-create:tram": case "-create:trbs": case "-create:txmp": case "-create:m3gm": case "-create:subt": case "-create:oban": case "-create": case "create": return CreateGeneric(args); case "-grid:create": return CreateGrids(args); case "-create:level": return ImportLevel(args); case "-room:extract": return ExtractRooms(args); case "film2xml": return ConvertFilm2Xml(args); default: Console.Error.WriteLine("Unknown command {0}", args[0]); return 1; } } private static string[] AddSearchPaths(string[] args) { List newArgs = new List(); for (int i = 0; i < args.Length; i++) { if (args[i] == "-search") { i++; if (i < args.Length) fileManager.AddSearchPath(args[i]); } else { newArgs.Add(args[i]); } } return newArgs.ToArray(); } private static int Help(string[] args) { if (args.Length > 1 && args[1] == "enums") { HelpEnums(); return 0; } Console.WriteLine("{0} [options] datfile", Environment.GetCommandLineArgs()[0]); Console.WriteLine(); Console.WriteLine("Options:"); Console.WriteLine("\t-export \t\tExport a Oni .dat file to directory"); Console.WriteLine("\t-import \t\tImport a Oni .dat file from directory"); Console.WriteLine("\t\t\t\t\tTarget file format is determined from source files (when possible)"); Console.WriteLine("\t-import:sep \t\tImport a Oni .dat file from directory"); Console.WriteLine("\t\t\t\t\tCreate a .dat file that uses .raw and .sep binary files (Mac and PC Demo)"); Console.WriteLine("\t-import:nosep \tImport a Oni .dat file from directory"); Console.WriteLine("\t\t\t\t\tCreate a .dat file that uses only .raw binary file (PC)"); Console.WriteLine(); Console.WriteLine("\t-extract:dds \tExtracts all textures (TXMP) from a Oni .dat/.oni file in DDS format"); Console.WriteLine("\t-extract:tga \tExtracts all textures (TXMP) from a Oni .dat/.oni file in TGA format"); Console.WriteLine("\t-extract:png \tExtracts all textures (TXMP) from a Oni .dat/.oni file in PNG format"); Console.WriteLine("\t-extract:wav \tExtracts all sounds (SNDD) from a Oni .dat/.oni file in WAV format"); Console.WriteLine("\t-extract:aif \tExtracts all sounds (SNDD) from a Oni .dat/.oni file in AIF format"); Console.WriteLine("\t-extract:txt \tExtracts all subtitles (SUBT) from a Oni .dat/.oni file in TXT format"); Console.WriteLine("\t-extract:obj \tExtracts all M3GM and ONCC instances to Wavefront OBJ files"); Console.WriteLine("\t-extract:dae \tExtracts all M3GM and ONCC instances to Collada files"); Console.WriteLine("\t-extract:xml \tExtracts all instances to XML files"); Console.WriteLine(); Console.WriteLine("\t-create:txmp [-nomipmaps] [-nouwrap] [-novwrap] [-format:bgr|rgba|bgr555|bgra5551|bgra4444|dxt1] [-envmap:texture_name] [-large] image_file"); Console.WriteLine("\t-create:m3gm [-tex:texture_name] obj_file"); Console.WriteLine("\t-create:trbs dae_file"); Console.WriteLine("\t-create:subt txt_file"); Console.WriteLine("\t-create xml_file\tCreates an .oni file from an XML file"); Console.WriteLine(); Console.WriteLine("\t-grid:create -out: rooms_src.dae level_geometry1.dae level_geometry2.dae ...\tGenerates pathfinding grids"); Console.WriteLine(); Console.WriteLine("\t-list\t\t\t\tLists the named instances contained in datfile"); Console.WriteLine("\t-copy \t\tCopy an exported .oni file and its dependencies to directory"); Console.WriteLine("\t-move \t\tMove an exported .oni file and its dependencies to directory"); Console.WriteLine("\t-move:overwrite \tMove an exported .oni file and its dependencies to directory"); Console.WriteLine("\t\t\t\t\tOverwrites any existing files"); Console.WriteLine("\t-move:delete \tMove an exported .oni file and its dependencies to directory"); Console.WriteLine("\t\t\t\t\tDeletes files at source when they already exist at destination"); Console.WriteLine("\t-deps\t\t\t\tGet a list of exported .oni files the specified files depends on"); Console.WriteLine("\t-version\t\t\tShow OniSplit versions"); Console.WriteLine("\t-help\t\t\t\tShow this help"); Console.WriteLine("\t-help enums\t\t\tShow a list of enums and flags used in XML files"); Console.WriteLine(); return 0; } private static void HelpEnums() { WriteEnums(typeof(InstanceMetadata)); WriteEnums(typeof(ObjectMetadata)); Console.WriteLine("-----------------------------------------------------"); Console.WriteLine("Particles enums"); Console.WriteLine("-----------------------------------------------------"); Console.WriteLine(); Utils.WriteEnum(typeof(Particles.ParticleFlags1)); Utils.WriteEnum(typeof(Particles.ParticleFlags2)); Utils.WriteEnum(typeof(Particles.EmitterFlags)); Utils.WriteEnum(typeof(Particles.EmitterOrientation)); Utils.WriteEnum(typeof(Particles.EmitterPosition)); Utils.WriteEnum(typeof(Particles.EmitterRate)); Utils.WriteEnum(typeof(Particles.EmitterSpeed)); Utils.WriteEnum(typeof(Particles.EmitterDirection)); Utils.WriteEnum(typeof(Particles.DisableDetailLevel)); Utils.WriteEnum(typeof(Particles.AttractorSelector)); Utils.WriteEnum(typeof(Particles.AttractorTarget)); Utils.WriteEnum(typeof(Particles.EventType)); Utils.WriteEnum(typeof(Particles.SpriteType)); Utils.WriteEnum(typeof(Particles.StorageType)); Utils.WriteEnum(typeof(Particles.ValueType)); Console.WriteLine("-----------------------------------------------------"); Console.WriteLine("Object enums"); Console.WriteLine("-----------------------------------------------------"); Console.WriteLine(); Utils.WriteEnum(typeof(Physics.ObjectSetupFlags)); Utils.WriteEnum(typeof(Physics.ObjectPhysicsType)); Utils.WriteEnum(typeof(Physics.ObjectAnimationFlags)); } private static void WriteEnums(Type type) { #if !NETCORE foreach (var enumType in type.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic)) { if (!enumType.IsEnum) continue; Utils.WriteEnum(enumType); } #endif } private static int Unpack(string[] args) { if (args.Length < 3) { Console.Error.WriteLine("Invalid command line."); return 1; } var prefixes = new List(); string outputDirPath = null; string sourceFilePath = null; foreach (string arg in args) { if (arg.StartsWith("-export", StringComparison.Ordinal)) { string prefix = null; int i = arg.IndexOf(':'); if (i != -1) prefix = arg.Substring(i + 1); if (!string.IsNullOrEmpty(prefix)) prefixes.Add(prefix); } else if (outputDirPath == null) { outputDirPath = Path.GetFullPath(arg); } else if (sourceFilePath == null) { sourceFilePath = Path.GetFullPath(arg); } } var unpacker = new DatUnpacker(fileManager, outputDirPath); if (prefixes.Count > 0) unpacker.NameFilter = Utils.WildcardToRegex(prefixes); unpacker.ExportFiles(new[] { sourceFilePath }); return 0; } private static int ExportTextures(string[] args) { if (args.Length < 3) { Console.Error.WriteLine("Invalid command line."); return 1; } int i = args[0].IndexOf(':'); string fileType = null; if (i != -1) fileType = args[0].Substring(i + 1); var outputDirPath = Path.GetFullPath(args[1]); var sourceFilePaths = GetFileList(args, 2); var exporter = new Oni.Motoko.TextureExporter(fileManager, outputDirPath, fileType); exporter.ExportFiles(sourceFilePaths); return 0; } private static int ExportSounds(string[] args) { if (args.Length < 3) { Console.Error.WriteLine("Invalid command line."); return 1; } int i = args[0].IndexOf(':'); string fileType = null; if (i != -1) fileType = args[0].Substring(i + 1); var outputDirPath = Path.GetFullPath(args[1]); var sourceFilePaths = GetFileList(args, 2); SoundExporter exporter; var noDemo = args.Any(a => a == "-nodemo"); switch (fileType) { case "aif": exporter = new AifExporter(fileManager, outputDirPath, noDemo); break; case "wav": exporter = new WavExporter(fileManager, outputDirPath, false, noDemo); break; case "pcm": exporter = new WavExporter(fileManager, outputDirPath, true, noDemo); break; default: throw new NotSupportedException(string.Format("Unsupported file type {0}", fileType)); } exporter.ExportFiles(sourceFilePaths); return 0; } private static int ExportGeometry(string[] args) { if (args.Length < 3) { Console.Error.WriteLine("Invalid command line."); return 1; } int i = args[0].IndexOf(':'); string fileType = null; if (i != -1) fileType = args[0].Substring(i + 1); var outputDirPath = Path.GetFullPath(args[1]); var sourceFilePaths = GetFileList(args, 2); var exporter = new DaeExporter(args, fileManager, outputDirPath, fileType); exporter.ExportFiles(sourceFilePaths); return 0; } private static int ExportSubtitles(string[] args) { if (args.Length < 3) { Console.Error.WriteLine("Invalid command line."); return 1; } var outputDirPath = Path.GetFullPath(args[1]); var sourceFilePaths = GetFileList(args, 2); var exporter = new SubtitleExporter(fileManager, outputDirPath); exporter.ExportFiles(sourceFilePaths); return 0; } private static int ExportXml(string[] args) { if (args.Length < 3) { Console.Error.WriteLine("Invalid command line."); return 1; } var outputDirPath = Path.GetFullPath(args[1]); var sourceFilePaths = GetFileList(args, 2); var exporter = new XmlExporter(fileManager, outputDirPath) { Recursive = args.Any(a => a == "-recurse"), MergeAnimations = args.Any(a => a == "-anim-merge"), NoAnimation = args.Any(a => a == "-noanim") }; var animBodyFilePath = args.FirstOrDefault(a => a.StartsWith("-anim-body:", StringComparison.Ordinal)); if (animBodyFilePath != null) { animBodyFilePath = Path.GetFullPath(animBodyFilePath.Substring("-anim-body:".Length)); var file = fileManager.OpenFile(animBodyFilePath); exporter.AnimationBody = Totoro.BodyDatReader.Read(file.Descriptors[0]); } exporter.ExportFiles(sourceFilePaths); return 0; } private static int Pack(string[] args) { if (args.Length < 3) { Console.Error.WriteLine("Invalid command line."); return 1; } var packer = new DatPacker(); var inputPaths = new List(); if (args[0] == "pack") { for (int i = 1; i < args.Length; i++) { var arg = args[i]; if (arg == "-out") { i++; packer.TargetFilePath = Path.GetFullPath(args[i]); continue; } if (arg.StartsWith("-type:", StringComparison.Ordinal)) { switch (arg.Substring(6)) { case "nosep": case "pc": packer.TargetTemplateChecksum = InstanceFileHeader.OniPCTemplateChecksum; break; case "sep": case "pcdemo": case "macintel": packer.TargetTemplateChecksum = InstanceFileHeader.OniMacTemplateChecksum; break; case "ppc": packer.TargetTemplateChecksum = InstanceFileHeader.OniMacTemplateChecksum; packer.TargetBigEndian = true; break; default: throw new ArgumentException(string.Format("Unknown output type {0}", arg.Substring(6))); } continue; } if (Directory.Exists(arg)) { arg = Path.GetFullPath(arg); Console.WriteLine("Reading directory {0}", arg); inputPaths.AddRange(Directory.GetFiles(arg, "*.oni", SearchOption.AllDirectories)); } else { var dirPath = Path.GetDirectoryName(arg); var fileSpec = Path.GetFileName(arg); if (string.IsNullOrEmpty(dirPath)) dirPath = Directory.GetCurrentDirectory(); else dirPath = Path.GetFullPath(dirPath); if (Directory.Exists(dirPath)) { foreach (string filePath in Directory.GetFiles(dirPath, fileSpec)) { Console.WriteLine("Reading {0}", filePath); inputPaths.Add(filePath); } } } } packer.Pack(fileManager, inputPaths); } else { switch (args[0]) { case "-import:nosep": case "-import:pc": packer.TargetTemplateChecksum = InstanceFileHeader.OniPCTemplateChecksum; break; case "-import:sep": case "-import:pcdemo": case "-import:macintel": packer.TargetTemplateChecksum = InstanceFileHeader.OniMacTemplateChecksum; break; case "-import:ppc": packer.TargetTemplateChecksum = InstanceFileHeader.OniMacTemplateChecksum; packer.TargetBigEndian = true; break; } for (int i = 1; i < args.Length - 1; i++) inputPaths.Add(Path.GetFullPath(args[i])); packer.TargetFilePath = Path.GetFullPath(args[args.Length - 1]); packer.Import(fileManager, inputPaths.ToArray()); } return 0; } private static int Copy(string[] args) { if (args.Length < 3) { Console.Error.WriteLine("Invalid command line."); return 1; } var outputDirPath = Path.GetFullPath(args[1]); var inputFilePaths = GetFileList(args, 2); var copy = new InstanceFileOperations(); copy.Copy(fileManager, inputFilePaths, outputDirPath); return 0; } private static int Move(string[] args) { if (args.Length < 3) { Console.Error.WriteLine("Invalid command line."); return 1; } var outputDirPath = Path.GetFullPath(args[1]); var inputFilePaths = GetFileList(args, 2); var copy = new InstanceFileOperations(); if (args[0] == "-move:delete") copy.MoveDelete(fileManager, inputFilePaths, outputDirPath); else if (args[0] == "-move:overwrite") copy.MoveOverwrite(fileManager, inputFilePaths, outputDirPath); else copy.Move(fileManager, inputFilePaths, outputDirPath); return 0; } private static int List(string[] args) { if (args.Length < 2) { Console.Error.WriteLine("Invalid command line."); return 1; } string sourceFilePath = Path.GetFullPath(args[1]); var file = fileManager.OpenFile(sourceFilePath); foreach (var descriptor in file.GetNamedDescriptors()) Console.WriteLine(descriptor.FullName); return 0; } private static int GetDependencies(string[] args) { if (args.Length < 2) { Console.Error.WriteLine("Invalid command line."); return 1; } var inputFilePaths = GetFileList(args, 1); InstanceFileOperations copy = new InstanceFileOperations(); copy.GetDependencies(fileManager, inputFilePaths); return 0; } private static int PrintVersion() { Console.WriteLine("OniSplit version {0}", Utils.Version); return 0; } private static int CreateGrids(string[] args) { if (args.Length < 2) { Console.Error.WriteLine("Invalid command line."); return 1; } var filePaths = GetFileList(args, 1); var roomsScene = Dae.Reader.ReadFile(filePaths[0]); var geometryMesh = Akira.AkiraDaeReader.Read(filePaths.Skip(1)); var gridBuilder = new Akira.RoomGridBuilder(roomsScene, geometryMesh); string outputDirPath = null; foreach (string arg in args) { if (arg.StartsWith("-out:", StringComparison.Ordinal)) outputDirPath = Path.GetFullPath(arg.Substring(5)); } if (string.IsNullOrEmpty(outputDirPath)) { Console.Error.WriteLine("Output path must be specified"); return 1; } gridBuilder.Build(); AkiraDaeWriter.WriteRooms(gridBuilder.Mesh, Path.GetFileNameWithoutExtension(filePaths[0]), outputDirPath); return 0; } private static int ExtractRooms(string[] args) { if (args.Length < 2) { Console.Error.WriteLine("Invalid command line."); return 1; } string outputFilePath = null; foreach (string arg in args) { if (arg.StartsWith("-out:", StringComparison.Ordinal)) outputFilePath = Path.GetFullPath(arg.Substring(5)); } if (string.IsNullOrEmpty(outputFilePath)) { Console.Error.WriteLine("Output file path must be specified"); return 1; } var extractor = new Akira.RoomExtractor(GetFileList(args, 1), outputFilePath); extractor.Extract(); return 0; } private static int CreateAkira(string[] args) { if (args.Length < 2) { Console.Error.WriteLine("Invalid command line."); return 1; } var outputDirPath = Path.GetFullPath(args[1]); var filePaths = GetFileList(args, 2); Directory.CreateDirectory(outputDirPath); var importedFiles = new Set(StringComparer.OrdinalIgnoreCase); var taskQueue = new Queue(); foreach (string filePath in filePaths) importedFiles.Add(filePath); var importer = new AkiraImporter(args); Console.WriteLine("Importing {0}", filePaths[0]); importer.Import(filePaths, outputDirPath); QueueTasks(importedFiles, taskQueue, importer); ExecuteTasks(args, outputDirPath, importedFiles, taskQueue); return 0; } private static int CreateGeneric(string[] args) { if (args.Length < 2) { Console.Error.WriteLine("Invalid command line."); return 1; } var targetType = TemplateTag.NONE; int colonIndex = args[0].IndexOf(':'); if (colonIndex != -1) { string tagName = args[0].Substring(colonIndex + 1); targetType = (TemplateTag)Enum.Parse(typeof(TemplateTag), tagName, true); } string outputDirPath = Path.GetFullPath(args[1]); var filePaths = GetFileList(args, 2); Directory.CreateDirectory(outputDirPath); var importedFiles = new Set(StringComparer.OrdinalIgnoreCase); var importQueue = new Queue(); foreach (string filePath in filePaths) { if (importedFiles.Add(filePath)) importQueue.Enqueue(new ImporterTask(filePath, targetType)); } ExecuteTasks(args, outputDirPath, importedFiles, importQueue); return 0; } private static void ExecuteTasks(string[] args, string outputDirPath, Set importedFiles, Queue taskQueue) { while (taskQueue.Count > 0) { var task = taskQueue.Dequeue(); if (!File.Exists(task.FilePath)) { Console.Error.WriteLine("File {0} does not exist", task.FilePath); continue; } var importer = CreateImporterFromFileName(args, task); if (importer == null) { Console.Error.WriteLine("{0} files cannot be imported as {1}", Path.GetExtension(task.FilePath), task.Type); continue; } Console.WriteLine("Importing {0}", task.FilePath); importer.Import(task.FilePath, outputDirPath); QueueTasks(importedFiles, taskQueue, importer); } } private static Importer CreateImporterFromFileName(string[] args, ImporterTask task) { Importer importer = null; switch (Path.GetExtension(task.FilePath).ToLowerInvariant()) { case ".bin": importer = new BinImporter(); break; case ".xml": importer = new XmlImporter(args); break; case ".tga": case ".dds": case ".png": case ".jpg": case ".bmp": case ".tif": if (task.Type == TemplateTag.NONE || task.Type == TemplateTag.TXMP) importer = new Oni.Motoko.TextureImporter(args); break; case ".obj": case ".dae": if (task.Type == TemplateTag.NONE || task.Type == TemplateTag.M3GM) importer = new Motoko.GeometryImporter(args); else if (task.Type == TemplateTag.AKEV) importer = new AkiraImporter(args); else if (task.Type == TemplateTag.TRBS) importer = new Totoro.BodySetImporter(args); else if (task.Type == TemplateTag.OBAN) importer = new Physics.ObjectAnimationImporter(args); break; case ".wav": if (task.Type == TemplateTag.NONE || task.Type == TemplateTag.SNDD) importer = new WavImporter(args.Any(a => a == "-demo")); break; case ".aif": case ".aifc": case ".afc": if (task.Type == TemplateTag.NONE || task.Type == TemplateTag.SNDD) importer = new AifImporter(); break; case ".txt": if (task.Type == TemplateTag.NONE || task.Type == TemplateTag.SUBT) importer = new SubtitleImporter(); break; } return importer; } private static void QueueTasks(Set imported, Queue importQueue, Importer importer) { foreach (ImporterTask child in importer.Dependencies) { if (!imported.Contains(child.FilePath)) { imported.Add(child.FilePath); importQueue.Enqueue(child); } } } private static int ConvertFilm2Xml(string[] args) { if (args.Length < 3) { Console.Error.WriteLine("Invalid command line."); return 1; } var outputDirPath = Path.GetFullPath(args[1]); var inputFilePaths = GetFileList(args, 2); Directory.CreateDirectory(outputDirPath); foreach (string filePath in inputFilePaths) FilmToXmlConverter.Convert(filePath, outputDirPath); return 0; } private static int ImportLevel(string[] args) { if (args.Length < 2) { Console.Error.WriteLine("Invalid command line."); return 1; } var levelImporter = new Level.LevelImporter { Debug = args.Any(a => a == "-debug") }; var outputDirPath = Path.GetFullPath(args[1]); if (string.IsNullOrEmpty(outputDirPath)) { Console.Error.WriteLine("Output path must be specified"); return 1; } var inputFiles = GetFileList(args, 2); if (inputFiles.Count == 0) { Console.Error.WriteLine("No input files specified"); return 1; } if (inputFiles.Count > 1) { Console.Error.WriteLine("Too many input files specified, only one level can be created at a time"); return 1; } levelImporter.Import(inputFiles[0], outputDirPath); return 0; } private static List GetFileList(string[] args, int startIndex) { var fileSet = new Set(StringComparer.OrdinalIgnoreCase); var fileList = new List(); foreach (var arg in args.Skip(startIndex)) { if (arg[0] == '-') continue; string dirPath = Path.GetDirectoryName(arg); string fileSpec = Path.GetFileName(arg); if (string.IsNullOrEmpty(dirPath)) dirPath = Directory.GetCurrentDirectory(); else dirPath = Path.GetFullPath(dirPath); if (Directory.Exists(dirPath)) { foreach (string filePath in Directory.GetFiles(dirPath, fileSpec)) { if (fileSet.Add(filePath)) fileList.Add(filePath); } } } if (fileList.Count == 0) throw new ArgumentException("No input files found"); return fileList; } } }