|
@@ -0,0 +1,220 @@
|
|
|
+using System;
|
|
|
+using System.Collections.Generic;
|
|
|
+using System.Drawing;
|
|
|
+using System.Drawing.Imaging;
|
|
|
+using System.IO;
|
|
|
+using System.Linq;
|
|
|
+using System.Runtime.InteropServices;
|
|
|
+using ArcToolkitCLI.Commands.Options;
|
|
|
+using ArcToolkitCLI.Util;
|
|
|
+using CommandLine;
|
|
|
+using Squish;
|
|
|
+
|
|
|
+namespace ArcToolkitCLI.Commands.Converters
|
|
|
+{
|
|
|
+ [Verb("tex", HelpText = "Convert TEX files into PNG and back")]
|
|
|
+ internal class TexConverter : IConverterCommand, IInputOptions, IOutputOptions
|
|
|
+ {
|
|
|
+ private const string TEX_TAG = "CM3D2_TEX";
|
|
|
+ private const int TEX_VERSION = 1010;
|
|
|
+ private readonly byte[] PNG_HEADER = {137, 80, 78, 71, 13, 10, 26, 10};
|
|
|
+
|
|
|
+ private readonly Dictionary<TextureFormat, Action<string, byte[], int, int, TextureFormat>> textureLoaders;
|
|
|
+
|
|
|
+ public TexConverter()
|
|
|
+ {
|
|
|
+ textureLoaders = new Dictionary<TextureFormat, Action<string, byte[], int, int, TextureFormat>>
|
|
|
+ {
|
|
|
+ [TextureFormat.ARGB32] = ConvertFromPng,
|
|
|
+ [TextureFormat.RGB24] = ConvertFromPng,
|
|
|
+ [TextureFormat.DXT1] = ConvertFromDxt,
|
|
|
+ [TextureFormat.DXT5] = ConvertFromDxt
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ public int Run()
|
|
|
+ {
|
|
|
+ var files = Glob.EnumerateFiles(Input);
|
|
|
+ Directory.CreateDirectory(Output);
|
|
|
+
|
|
|
+ if (!files.Any())
|
|
|
+ {
|
|
|
+ Console.WriteLine("No files specified. Run `convert help tex` for help.");
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach (var file in files)
|
|
|
+ {
|
|
|
+ var result = 0;
|
|
|
+ switch (file.Extension.ToLowerInvariant())
|
|
|
+ {
|
|
|
+ case ".png":
|
|
|
+ if ((result = PngToTex(file.FullName)) != 0)
|
|
|
+ return result;
|
|
|
+ break;
|
|
|
+ case ".tex":
|
|
|
+ if ((result = TexToPng(file.FullName)) != 0)
|
|
|
+ return result;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ Console.WriteLine($"File {file.FullName} is neither .png nor .tex file. Skipping...");
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ [Value(0, HelpText = "Input PNG or TEX files")]
|
|
|
+ public IEnumerable<string> Input { get; set; }
|
|
|
+
|
|
|
+ public string Output { get; set; }
|
|
|
+
|
|
|
+
|
|
|
+ private bool ByteEquals(byte[] b1, byte[] b2)
|
|
|
+ {
|
|
|
+ var len = Math.Min(b1.Length, b2.Length);
|
|
|
+
|
|
|
+ for (var i = 0; i < len; i++)
|
|
|
+ if (b1[i] != b2[i])
|
|
|
+ return false;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ private int PngToTex(string file)
|
|
|
+ {
|
|
|
+ var img = File.ReadAllBytes(file);
|
|
|
+
|
|
|
+ if (!ByteEquals(img, PNG_HEADER))
|
|
|
+ {
|
|
|
+ Console.WriteLine($"File {file} is not a PNG file!");
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ var w = BitConverter.ToInt32(img, PNG_HEADER.Length + 4 + 4);
|
|
|
+ var h = BitConverter.ToInt32(img, PNG_HEADER.Length + 4 + 4 + 4);
|
|
|
+ using (var bw =
|
|
|
+ new BinaryWriter(File.Create(Path.Combine(Output, $"{Path.GetFileNameWithoutExtension(file)}.tex"))))
|
|
|
+ {
|
|
|
+ bw.Write(TEX_TAG);
|
|
|
+ bw.Write(TEX_VERSION);
|
|
|
+ bw.Write(string.Empty);
|
|
|
+ bw.Write(w);
|
|
|
+ bw.Write(h);
|
|
|
+ bw.Write((int) TextureFormat.ARGB32);
|
|
|
+ bw.Write(img.Length);
|
|
|
+ bw.Write(img);
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ private int TexToPng(string file)
|
|
|
+ {
|
|
|
+ using (var br = new BinaryReader(File.OpenRead(file)))
|
|
|
+ {
|
|
|
+ var tag = br.ReadString();
|
|
|
+
|
|
|
+ if (tag != TEX_TAG)
|
|
|
+ {
|
|
|
+ Console.WriteLine($"File {file} is not a valid TEX file!");
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ var version = br.ReadInt32();
|
|
|
+ br.ReadString();
|
|
|
+ var width = 0;
|
|
|
+ var height = 0;
|
|
|
+
|
|
|
+ var format = TextureFormat.ARGB32;
|
|
|
+
|
|
|
+ if (version >= 1010)
|
|
|
+ {
|
|
|
+ width = br.ReadInt32();
|
|
|
+ height = br.ReadInt32();
|
|
|
+ format = (TextureFormat) br.ReadInt32();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!Enum.IsDefined(typeof(TextureFormat), format))
|
|
|
+ {
|
|
|
+ Console.WriteLine($"File {file} has unsupported texture format: {format}");
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ var size = br.ReadInt32();
|
|
|
+ var data = new byte[size];
|
|
|
+ br.Read(data, 0, size);
|
|
|
+
|
|
|
+ if (version == 1000)
|
|
|
+ {
|
|
|
+ width = BitConverter.ToInt32(data, PNG_HEADER.Length + 4 + 4);
|
|
|
+ height = BitConverter.ToInt32(data, PNG_HEADER.Length + 4 + 4 + 4);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (textureLoaders.TryGetValue(format, out var saveTex))
|
|
|
+ {
|
|
|
+ saveTex(file, data, width, height, format);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Console.WriteLine($"File {file} uses format {format} that is not supported!");
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void ConvertFromPng(string file, byte[] data, int width, int height, TextureFormat format)
|
|
|
+ {
|
|
|
+ var ms = new MemoryStream(data);
|
|
|
+ var img = Image.FromStream(ms);
|
|
|
+ img.Save(Path.Combine(Output, $"{Path.GetFileNameWithoutExtension(file)}.png"));
|
|
|
+ img.Dispose();
|
|
|
+ ms.Dispose();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void ConvertFromDxt(string file, byte[] data, int width, int height, TextureFormat format)
|
|
|
+ {
|
|
|
+ var squishFlags = (SquishFlags) 0;
|
|
|
+
|
|
|
+ switch (format)
|
|
|
+ {
|
|
|
+ case TextureFormat.DXT1:
|
|
|
+ squishFlags |= SquishFlags.kDxt1;
|
|
|
+ break;
|
|
|
+ case TextureFormat.DXT5:
|
|
|
+ squishFlags |= SquishFlags.kDxt5;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ var outData = new byte[width * height * 4];
|
|
|
+ Squish.Squish.DecompressImage(outData, width, height, ref data, squishFlags);
|
|
|
+
|
|
|
+ for (var i = 0; i < width * height; i++)
|
|
|
+ {
|
|
|
+ var r = outData[i * 4];
|
|
|
+ outData[i * 4] = outData[i * 4 + 2];
|
|
|
+ outData[i * 4 + 2] = r;
|
|
|
+ }
|
|
|
+
|
|
|
+ var gch = GCHandle.Alloc(outData, GCHandleType.Pinned);
|
|
|
+ var img = new Bitmap(width, height, width * 4, PixelFormat.Format32bppArgb, gch.AddrOfPinnedObject());
|
|
|
+ img.RotateFlip(RotateFlipType.RotateNoneFlipY);
|
|
|
+
|
|
|
+ img.Save(Path.Combine(Output, $"{Path.GetFileNameWithoutExtension(file)}.png"));
|
|
|
+
|
|
|
+ img.Dispose();
|
|
|
+ gch.Free();
|
|
|
+ }
|
|
|
+
|
|
|
+ private enum TextureFormat
|
|
|
+ {
|
|
|
+ ARGB32 = 5,
|
|
|
+ RGB24 = 3,
|
|
|
+ DXT1 = 10,
|
|
|
+ DXT5 = 12
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|