diff --git a/README.md b/README.md index 435bf16..95d910d 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,17 @@ To specify a directory, you can use the `-o/--output` switch: $ kanimal-cli.exe kanim [NAME].png [NAME]_anim.bytes [NAME]_build.bytes -o my/output/path ``` +#### Batch conversion + +It is possible to batch convert *Oxygen Not Included* assets to Spriter files. + +1. Unpack the Unity asset bundles. Ensure that the root directory is `Assets`, which contains `Texture2D` and `TextAsset`. +2. Run the following command: +``` +$ kanimal-cli batch-convert /path/to/assets/directory +``` +3. The result files will be output in the `output/` directory relative to the current working directory. You can specify a different path with the `-o/--output` flag, as always. + ### scml → kanim The process is very similar to the previous one. @@ -56,12 +67,13 @@ Just like in the kanim → scml case, the files are output by default into the ` $ ./kanimal-cli convert -I [INPUT_FORMAT] -O [OUTPUT_FORMAT] [FILES ...] ``` -Other available switches are as follows: +Other available switches are as follows: | switch | effect | |--------|--------| | `-o/--output` | Specify an output directory | | `-v/--verbose` | Set verbosity level to DEBUG (default INFO)| | `-s/--silent` | Set verbosity level to FATAL (default INFO). This means no messages are logged on successful conversion, including warnings. | +|`-S/--strict` | Enforce strict conversion.| ### Kanim dump KSE supports dumping the contents of a kanim file to a (relatively) readable text file. The command is: diff --git a/kanimal-cli/Options.cs b/kanimal-cli/Options.cs index 76ecfa5..ed61154 100644 --- a/kanimal-cli/Options.cs +++ b/kanimal-cli/Options.cs @@ -15,6 +15,12 @@ internal abstract class ProgramOptions public string OutputPath { get; set; } = "output"; } + internal abstract class ConversionOptions : ProgramOptions + { + [Option('S', "strict", Required = false, HelpText = "When writing to scml, enabling this flag ")] + public bool Strict { get; set; } + } + [Verb("dump", HelpText = "Output a dump of the specified kanim.")] internal class DumpOptions : ProgramOptions { @@ -23,7 +29,7 @@ internal class DumpOptions : ProgramOptions // For ones with Output and Input specifiers [Verb("convert", HelpText = "Convert between formats.")] - internal class GenericOptions : ProgramOptions + internal class GenericOptions : ConversionOptions { [Option('I', "input-format", Required = true, HelpText = "The input format, from [kanim, scml]")] public string InputFormat { get; set; } @@ -35,14 +41,20 @@ internal class GenericOptions : ProgramOptions } [Verb("scml", HelpText = "Convert kanim to scml. Convenience verb equivalent to 'convert -I kanim -O scml'.")] - internal class KanimToScmlOptions : ProgramOptions + internal class KanimToScmlOptions : ConversionOptions { [Value(0)] public IEnumerable Files { get; set; } } [Verb("kanim", HelpText = "Convert scml to kanim. Convenience verb equivalent to 'convert -I scml -O kanim'.")] - internal class ScmlToKanimOptions : ProgramOptions + internal class ScmlToKanimOptions : ConversionOptions { [Value(0)] public string ScmlFile { get; set; } } + + [Verb("batch-convert", HelpText = "Given an Assets/ directory, attempt to batch convert kanim to scml.")] + internal class BatchConvertOptions : ConversionOptions + { + [Value(0)] public string AssetDirectory { get; set; } + } } \ No newline at end of file diff --git a/kanimal-cli/Program.cs b/kanimal-cli/Program.cs index 1ad7dcb..e83f071 100644 --- a/kanimal-cli/Program.cs +++ b/kanimal-cli/Program.cs @@ -2,9 +2,13 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.ExceptionServices; using NLog; using CommandLine; using kanimal; +using NLog.Config; +using NLog.Filters; +using NLog.Fluent; namespace kanimal_cli { @@ -12,7 +16,7 @@ internal class Program { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - private static void SetVerbosity(ProgramOptions o) + private static LoggingConfiguration GetLoggerConfig(ProgramOptions o) { var config = new NLog.Config.LoggingConfiguration(); var targetConsole = new NLog.Targets.ConsoleTarget("logconsole"); @@ -31,10 +35,15 @@ private static void SetVerbosity(ProgramOptions o) else config.AddRule(LogLevel.Info, LogLevel.Fatal, targetConsole); - LogManager.Configuration = config; + return config; + } + + private static void SetVerbosity(ProgramOptions o) + { + LogManager.Configuration = GetLoggerConfig(o); } - private static void Convert(string inputFormat, string outputFormat, List files, ProgramOptions opt) + private static void Convert(string inputFormat, string outputFormat, List files, ConversionOptions opt) { SetVerbosity(opt); @@ -57,10 +66,11 @@ private static void Convert(string inputFormat, string outputFormat, List path.EndsWith(".png")); var build = files.Find(path => path.EndsWith("build.bytes")); var anim = files.Find(path => path.EndsWith("anim.bytes")); + reader = new KanimReader( - new FileStream(png, FileMode.Open), new FileStream(build, FileMode.Open), - new FileStream(anim, FileMode.Open)); + new FileStream(anim, FileMode.Open), + new FileStream(png, FileMode.Open)); reader.Read(); break; default: @@ -76,6 +86,7 @@ private static void Convert(string inputFormat, string outputFormat, List(args) + Parser.Default.ParseArguments(args) .WithParsed(o => Convert( "kanim", "scml", @@ -107,7 +118,7 @@ private static void Main(string[] args) var png = files.Find(path => path.EndsWith(".png")); var build = files.Find(path => path.EndsWith("build.bytes")); var anim = files.Find(path => path.EndsWith("anim.bytes")); - + Directory.CreateDirectory(o.OutputPath); Utilities.Dump = new StreamWriter(new FileStream(Path.Join(o.OutputPath, "dump.log"), FileMode.Create)); var reader = new KanimReader( @@ -122,7 +133,76 @@ private static void Main(string[] args) "kanim", new List {o.ScmlFile}, o)) - .WithParsed(o => Convert(o.InputFormat, o.OutputFormat, o.Files.ToList(), o)); + .WithParsed(o => Convert(o.InputFormat, o.OutputFormat, o.Files.ToList(), o)) + .WithParsed(o => + { + // Silence Info output from kanimal + var config = new LoggingConfiguration(); + var target = new NLog.Targets.ConsoleTarget("logconsole"); + target.Layout = "[${level}] ${message}"; + var loggingRule1 = new LoggingRule("kanimal_cli.*", target); + loggingRule1.SetLoggingLevels(LogLevel.Info, LogLevel.Fatal); + config.LoggingRules.Add(loggingRule1); + var loggingRule2 = new LoggingRule("kanimal.*", target); + loggingRule2.SetLoggingLevels(LogLevel.Warn, LogLevel.Fatal); + config.LoggingRules.Add(loggingRule2); + LogManager.Configuration = config; + + if (!Directory.Exists(Path.Join(o.AssetDirectory, "Texture2D"))) + { + Logger.Fatal($"The path \"{o.AssetDirectory}/Texture2D\" does not exist."); + Environment.Exit((int)ExitCodes.IncorrectArguments); + } + if (!Directory.Exists(Path.Join(o.AssetDirectory, "TextAsset"))) + { + Logger.Fatal($"The path \"{o.AssetDirectory}/TextAsset\" does not exist."); + Environment.Exit((int)ExitCodes.IncorrectArguments); + } + + foreach (var filepath in Directory.GetFiles(Path.Join(o.AssetDirectory, "Texture2D"), "*.png")) + { + var filename = Path.GetFileName(filepath); + var basename = Utilities.GetSpriteBaseName(filename); + if (basename == "") + { + Logger.Warn($"Skipping \"{filename}\" as it does not seem to be a valid anim."); + continue; + } + var png = new FileStream(filepath, FileMode.Open); + + var animPath = Path.Join(o.AssetDirectory, "TextAsset", $"{basename}_anim.bytes"); + var buildPath = Path.Join(o.AssetDirectory, "TextAsset", $"{basename}_build.bytes"); + if (!File.Exists(animPath)) + { + Logger.Warn($"Skipping \"{basename}\" because it does not have a corresponding anim.bytes file."); + continue; + } + + if (!File.Exists(buildPath)) + { + Logger.Warn($"Skipping \"{basename}\" because it does not have a corresponding build.bytes file."); + continue; + } + var anim = new FileStream(animPath, FileMode.Open); + var build = new FileStream(buildPath, FileMode.Open); + + var reader = new KanimReader(build, anim, png); + try + { + reader.Read(); + var writer = new ScmlWriter(reader); + writer.SaveToDir(Path.Join(o.OutputPath, reader.BuildData.Name)); + } + catch (Exception e) + { + Logger.Error($"The following error occured while exporting \"{reader.BuildData.Name}\":"); + Logger.Error(e.ToString()); + Logger.Error("Skipping."); + continue; + } + Logger.Info($"Exported \"{reader.BuildData.Name}\"."); + } + }); } } } \ No newline at end of file