|  | @@ -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
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 |