Kaynağa Gözat

Add ability to convert TEX

ghorsington 4 yıl önce
ebeveyn
işleme
f06b88480b

+ 3 - 4
ArcToolkitCLI/App.config

@@ -1,7 +1,6 @@
-<?xml version="1.0" encoding="utf-8"?>
-
+<?xml version="1.0" encoding="utf-8"?>
 <configuration>
   <startup>
-    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
+    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/>
   </startup>
-</configuration>
+</configuration>

+ 5 - 0
ArcToolkitCLI/ArcToolkitCLI.csproj

@@ -45,8 +45,12 @@
     <Reference Include="Glob, Version=1.1.3.0, Culture=neutral, processorArchitecture=MSIL">
       <HintPath>..\packages\Glob.1.1.3\lib\net46\Glob.dll</HintPath>
     </Reference>
+    <Reference Include="LibSquishNet, Version=1.11.5792.20541, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>..\packages\LibSquishNet.1.11.5792.20541\lib\net46\LibSquishNet.dll</HintPath>
+    </Reference>
     <Reference Include="System" />
     <Reference Include="System.Core" />
+    <Reference Include="System.Drawing" />
     <Reference Include="System.IO.Abstractions, Version=3.0.0.0, Culture=neutral, PublicKeyToken=96bf224d23c43e59, processorArchitecture=MSIL">
       <HintPath>..\packages\System.IO.Abstractions.3.0.10\lib\net40\System.IO.Abstractions.dll</HintPath>
     </Reference>
@@ -64,6 +68,7 @@
     <Compile Include="Commands\ConvertCommand.cs" />
     <Compile Include="Commands\Converters\IConverterCommand.cs" />
     <Compile Include="Commands\Converters\NeiConverter.cs" />
+    <Compile Include="Commands\Converters\TexConverter.cs" />
     <Compile Include="Commands\DecryptCommand.cs" />
     <Compile Include="Commands\ExtractCommand.cs" />
     <Compile Include="Commands\ICommand.cs" />

+ 5 - 1
ArcToolkitCLI/Commands/ConvertCommand.cs

@@ -14,7 +14,11 @@ namespace ArcToolkitCLI.Commands
             var commandTypes = typeof(ConvertCommand).Assembly.GetTypes()
                 .Where(t => !t.IsInterface && !t.IsAbstract && typeof(IConverterCommand).IsAssignableFrom(t)).ToArray();
 
-            return new Parser(with => with.IgnoreUnknownArguments = true).ParseArguments(Environment.GetCommandLineArgs().Skip(2), commandTypes)
+            return new Parser(with =>
+                {
+                    with.IgnoreUnknownArguments = true;
+                    with.HelpWriter = Parser.Default.Settings.HelpWriter;
+                }).ParseArguments(Environment.GetCommandLineArgs().Skip(2), commandTypes)
                 .MapResult((object t) => ((IConverterCommand)t).Run(), errs => 1);
         }
     }

+ 220 - 0
ArcToolkitCLI/Commands/Converters/TexConverter.cs

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

+ 6 - 1
ArcToolkitCLI/Program.cs

@@ -10,7 +10,12 @@ namespace ArcToolkitCLI
         {
             var commandTypes = typeof(Program).Assembly.GetTypes()
                 .Where(t => !t.IsInterface && !t.IsAbstract && typeof(ICommand).IsAssignableFrom(t)).ToArray();
-            return new Parser(with => with.IgnoreUnknownArguments = true).ParseArguments(args, commandTypes)
+            return new Parser(with =>
+                {
+                    with.IgnoreUnknownArguments = true;
+                    with.AutoHelp = true;
+                    with.HelpWriter = Parser.Default.Settings.HelpWriter;
+                }).ParseArguments(args, commandTypes)
                 .MapResult((object t) => ((ICommand) t).Run(), errs => 1);
         }
     }

+ 1 - 0
ArcToolkitCLI/packages.config

@@ -3,6 +3,7 @@
   <package id="Glob" version="1.1.3" targetFramework="net461" />
   <package id="ILRepack" version="2.0.16" targetFramework="net461" />
   <package id="ILRepack.Lib.MSBuild.Task" version="2.0.16.1" targetFramework="net461" />
+  <package id="LibSquishNet" version="1.11.5792.20541" targetFramework="net461" />
   <package id="System.IO.Abstractions" version="3.0.10" targetFramework="net461" />
   <package id="System.IO.FileSystem" version="4.3.0" targetFramework="net461" />
   <package id="System.IO.FileSystem.Primitives" version="4.3.0" targetFramework="net461" />