using LibDgf.Dat; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; using System; using System.Collections.Generic; using System.IO; using Path = System.IO.Path; using LibDgf.Aqualead; using LibDgf.Aqualead.Image.Conversion; using LibDgf; using LibDgf.Txm; using LibDgf.Aqualead.Image; using LibDgf.Aqualead.Texture; using System.Diagnostics; using SixLabors.ImageSharp.Processing; using LibDgf.Graphics; using System.Threading.Tasks; using McMaster.Extensions.CommandLineUtils; namespace DgfTxmConvert { class Program { const string ETC1TOOL_PATH = @"etc1tool.exe"; static List imageConverters; static int Main(string[] args) { imageConverters = new List { new PkmConverter(), //new KtxConverter(), new PngConverter(), }; var app = new CommandLineApplication { Name = Path.GetFileName(Environment.GetCommandLineArgs()[0]), FullName = "DDG Final TXM Converter" }; app.Command("convert-dat", config => { config.FullName = "Convert DAT to images"; config.Description = "Converts each individual TXM in a DAT into PNGs."; var datPathArg = config.Argument("datPath", "Path of the DAT to extract").IsRequired(); datPathArg.Accepts().ExistingFile(); config.HelpOption(); config.OnExecute(() => { ConvertDat(datPathArg.Value); }); }); app.Command("convert-txm", config => { config.FullName = "Convert TXM to image"; config.Description = "Converts TXM to PNG."; var txmPathArg = config.Argument("txmPath", "Path of the TXM to convert").IsRequired(); txmPathArg.Accepts().ExistingFile(); config.HelpOption(); config.OnExecute(() => { ConvertTxm(txmPathArg.Value); }); }); app.Command("replace-dat", config => { config.FullName = "Replace image in DAT"; config.Description = "Converts list of images and replaces entries in DAT file"; var srcDatPath = config.Argument("srcDatPath", "Path of the source DAT").IsRequired(); srcDatPath.Accepts().ExistingFile(); var listPath = config.Argument("listPath", "Path of the image replacement list").IsRequired(); listPath.Accepts().ExistingFile(); var destDatPath = config.Argument("destDatPath", "Path to output DAT to").IsRequired(); listPath.Accepts().LegalFilePath(); config.HelpOption(); config.OnExecute(() => { ReplaceDatImages(srcDatPath.Value, destDatPath.Value, listPath.Value); }); }); app.VersionOptionFromAssemblyAttributes(System.Reflection.Assembly.GetExecutingAssembly()); app.HelpOption(); app.OnExecute(() => { app.ShowHelp(); return 1; }); try { return app.Execute(args); } catch (CommandParsingException ex) { Console.Error.WriteLine(ex.Message); return 1; } catch (Exception ex) { Console.Error.WriteLine("Error while processing: {0}", ex); return -1; } } static void ReplaceDatImages(string srcDatPath, string destDatPath, string replacementList) { List tempPaths = new List(); try { using (DatReader dat = new DatReader(File.OpenRead(srcDatPath))) { DatBuilder builder = new DatBuilder(dat); using (StreamReader sr = File.OpenText(replacementList)) { while (!sr.EndOfStream) { var line = sr.ReadLine(); var lineSplit = line.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries); if (lineSplit.Length != 2) throw new InvalidDataException($"Invalid line \"{line}\"."); if (!int.TryParse(lineSplit[0], out var imageIndex)) throw new InvalidDataException($"Invalid index on line \"{line}\"."); string tempPath = Path.GetTempFileName(); tempPaths.Add(tempPath); using (FileStream fs = File.Create(tempPath)) { TxmConversion.ConvertImageToTxm(lineSplit[1], fs); } builder.ReplacementEntries.Add(new DatBuilder.ReplacementEntry { Index = imageIndex, SourceFile = tempPath }); } } using (FileStream fs = File.Create(destDatPath)) { builder.Build(fs); } } } finally { foreach (var path in tempPaths) { File.Delete(path); } } } static void ConvertDat(string path) { using (Stream fs = Utils.CheckDecompress(File.OpenRead(path))) using (DatReader dat = new DatReader(fs)) { for (int i = 0; i < dat.EntriesCount; ++i) { using (MemoryStream subfile = new MemoryStream(dat.GetData(i))) { string outPath = path + $"_{i}.png"; Console.WriteLine(outPath); try { TxmConversion.ConvertTxmToPng(subfile, outPath); } catch (NotSupportedException) { File.WriteAllBytes(path + $"_{i}.txm", subfile.ToArray()); throw; } } } } } static void BulkConvertDat(string inPath, string filter, bool recursive) { foreach (var path in Directory.GetFiles(inPath, filter, recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)) { ConvertDat(path); } } static void ConvertTxm(string path) { using (Stream fs = Utils.CheckDecompress(File.OpenRead(path))) { string outPath = Path.ChangeExtension(path, ".png"); TxmConversion.ConvertTxmToPng(fs, outPath); } } static void BulkConvertTxm(string inPath, string filter, bool recursive) { foreach (var path in Directory.GetFiles(inPath, filter, recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)) { string outPath = Path.ChangeExtension(path, ".png"); Console.WriteLine(outPath); ConvertTxm(path); } } static void BulkConvertAtx(string inPath, string filter, bool recursive) { foreach (var path in Directory.GetFiles(inPath, filter, recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)) { Console.WriteLine(path); using (var fs = Utils.CheckDecompress(File.OpenRead(path))) { var tex = new AlTexture(); tex.Read(new BinaryReader(fs)); var img = new AlImage(); using (Stream ms = Utils.CheckDecompress(new MemoryStream(tex.ImageData))) { img.Read(new BinaryReader(ms)); ConvertAig(img, path); } } } } static void BulkConvertAtxMulti(string inPath, string filter, bool recursive) { foreach (var path in Directory.GetFiles(inPath, filter, recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)) { using (var fs = Utils.CheckDecompress(File.OpenRead(path))) { var tex = new AlTexture(); tex.Read(new BinaryReader(fs)); if (tex.ChildTextures.Count < 2) continue; Console.WriteLine(path); var img = new AlImage(); using (Stream ms = Utils.CheckDecompress(new MemoryStream(tex.ImageData))) { img.Read(new BinaryReader(ms)); if (img.PixelFormat != "BGRA") { Console.WriteLine("Not BGRA, skipping"); continue; } using (MemoryStream ims = new MemoryStream(img.Mipmaps[0])) using (var imgConv = PngConverter.ConvertBgra32(new BinaryReader(ims), (int)img.Width, (int)img.Height)) { foreach (var child in tex.ChildTextures) { // First mip only var bounds = child.Bounds[0]; using (var childImage = new Image(bounds.W, bounds.H)) { childImage.Mutate(ctx => ctx.DrawImage(imgConv, new Point(-bounds.X, -bounds.Y), 1f)); childImage.SaveAsPng($"{Path.ChangeExtension(path, null)}_{child.Id:x8}.png"); } } } } } } } static void BulkConvertAtxToPng(string inPath, string filter, bool recursive) { foreach (var path in Directory.GetFiles(inPath, filter, recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)) { Console.WriteLine(path); string fileExtension; // Export the texture (probably KTX) using (var fs = Utils.CheckDecompress(File.OpenRead(path))) { var tex = new AlTexture(); tex.Read(new BinaryReader(fs)); var img = new AlImage(); using (Stream ms = Utils.CheckDecompress(new MemoryStream(tex.ImageData))) { img.Read(new BinaryReader(ms)); fileExtension = ConvertAig(img, path); } } if (fileExtension != ".pkm") continue; // Convert main texture to PNG string mainPath = Path.ChangeExtension(path, fileExtension); var startInfo = new ProcessStartInfo(ETC1TOOL_PATH); startInfo.ArgumentList.Add(mainPath); startInfo.ArgumentList.Add("--decode"); startInfo.CreateNoWindow = false; startInfo.UseShellExecute = false; Process.Start(startInfo).WaitForExit(); // Convert alpha texture if present string altPath = $"{Path.ChangeExtension(path, null)}_alt{fileExtension}"; if (File.Exists(altPath)) { startInfo = new ProcessStartInfo(ETC1TOOL_PATH); startInfo.ArgumentList.Add(altPath); startInfo.ArgumentList.Add("--decode"); startInfo.CreateNoWindow = false; startInfo.UseShellExecute = false; Process.Start(startInfo).WaitForExit(); var mainPngPath = Path.ChangeExtension(mainPath, ".png"); var altPngPath = Path.ChangeExtension(altPath, ".png"); using (var mainImg = Image.Load(mainPngPath)) using (var altImg = Image.Load(altPngPath)) { using (var mergedImg = MergeAlpha(mainImg, altImg)) using (FileStream newFs = File.Create(mainPngPath)) { mergedImg.SaveAsPng(newFs); } } File.Delete(altPngPath); File.Delete(altPath); } File.Delete(mainPath); } } static Image MergeAlpha(Image main, Image alpha) { var newImg = new Image(main.Width, main.Height); for (int y = 0; y < newImg.Height; ++y) { var newRow = newImg.GetPixelRowSpan(y); var mainRow = main.GetPixelRowSpan(y); var alphaRow = alpha.GetPixelRowSpan(y); for (int x = 0; x < newImg.Width; ++x) { var mainPix = mainRow[x]; var alphaPix = alphaRow[x]; newRow[x] = new Rgba32(mainPix.R, mainPix.G, mainPix.B, alphaPix.R); } } return newImg; } static void BulkConvertAig(string inPath, string filter, bool recursive) { foreach (var path in Directory.GetFiles(inPath, filter, recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)) { Console.WriteLine(path); using (var fs = Utils.CheckDecompress(File.OpenRead(path))) { var img = new AlImage(); img.Read(new BinaryReader(fs)); ConvertAig(img, path); } } } static string ConvertAig(AlImage img, string path) { foreach (var converter in imageConverters) { if (converter.CanConvert(img.PixelFormat)) { string outPath = Path.ChangeExtension(path, converter.FileExtension); using (FileStream ofs = File.Create(outPath)) { converter.ConvertFromAl(img, ofs); } if (converter.HasAlternativeFile(img)) { outPath = Path.ChangeExtension(path, null); outPath = $"{outPath}_alt{converter.FileExtension}"; using (FileStream ofs = File.Create(outPath)) { converter.ConvertFromAlAlt(img, ofs); } } return converter.FileExtension; } } throw new Exception($"Cannot find converter for {img.PixelFormat}"); } } }