TexConverter.cs 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Drawing;
  4. using System.Drawing.Imaging;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Runtime.InteropServices;
  8. using ArcToolkitCLI.Commands.Options;
  9. using ArcToolkitCLI.Util;
  10. using CommandLine;
  11. using Squish;
  12. namespace ArcToolkitCLI.Commands.Converters
  13. {
  14. [Verb("tex", HelpText = "Convert TEX files into PNG and back")]
  15. internal class TexConverter : IConverterCommand, IInputOptions, IOutputOptions
  16. {
  17. private const string TEX_TAG = "CM3D2_TEX";
  18. private const int TEX_VERSION = 1010;
  19. private readonly byte[] PNG_HEADER = {137, 80, 78, 71, 13, 10, 26, 10};
  20. private readonly Dictionary<TextureFormat, Action<string, byte[], int, int, TextureFormat>> textureLoaders;
  21. public TexConverter()
  22. {
  23. textureLoaders = new Dictionary<TextureFormat, Action<string, byte[], int, int, TextureFormat>>
  24. {
  25. [TextureFormat.ARGB32] = ConvertFromPng,
  26. [TextureFormat.RGB24] = ConvertFromPng,
  27. [TextureFormat.DXT1] = ConvertFromDxt,
  28. [TextureFormat.DXT5] = ConvertFromDxt
  29. };
  30. }
  31. public int Run()
  32. {
  33. var files = Glob.EnumerateFiles(Input);
  34. Directory.CreateDirectory(Output);
  35. if (!files.Any())
  36. {
  37. Console.WriteLine("No files specified. Run `convert help tex` for help.");
  38. return 0;
  39. }
  40. foreach (var file in files)
  41. {
  42. var result = 0;
  43. switch (file.Extension.ToLowerInvariant())
  44. {
  45. case ".png":
  46. if ((result = PngToTex(file.FullName)) != 0)
  47. return result;
  48. break;
  49. case ".tex":
  50. if ((result = TexToPng(file.FullName)) != 0)
  51. return result;
  52. break;
  53. default:
  54. Console.WriteLine($"File {file.FullName} is neither .png nor .tex file. Skipping...");
  55. break;
  56. }
  57. }
  58. return 0;
  59. }
  60. [Value(0, HelpText = "Input PNG or TEX files")]
  61. public IEnumerable<string> Input { get; set; }
  62. public string Output { get; set; }
  63. private bool ByteEquals(byte[] b1, byte[] b2)
  64. {
  65. var len = Math.Min(b1.Length, b2.Length);
  66. for (var i = 0; i < len; i++)
  67. if (b1[i] != b2[i])
  68. return false;
  69. return true;
  70. }
  71. private int PngToTex(string file)
  72. {
  73. var img = File.ReadAllBytes(file);
  74. if (!ByteEquals(img, PNG_HEADER))
  75. {
  76. Console.WriteLine($"File {file} is not a PNG file!");
  77. return 1;
  78. }
  79. var w = BitConverter.ToInt32(img, PNG_HEADER.Length + 4 + 4);
  80. var h = BitConverter.ToInt32(img, PNG_HEADER.Length + 4 + 4 + 4);
  81. using (var bw =
  82. new BinaryWriter(File.Create(Path.Combine(Output, $"{Path.GetFileNameWithoutExtension(file)}.tex"))))
  83. {
  84. bw.Write(TEX_TAG);
  85. bw.Write(TEX_VERSION);
  86. bw.Write(string.Empty);
  87. bw.Write(w);
  88. bw.Write(h);
  89. bw.Write((int) TextureFormat.ARGB32);
  90. bw.Write(img.Length);
  91. bw.Write(img);
  92. }
  93. return 0;
  94. }
  95. private int TexToPng(string file)
  96. {
  97. using (var br = new BinaryReader(File.OpenRead(file)))
  98. {
  99. var tag = br.ReadString();
  100. if (tag != TEX_TAG)
  101. {
  102. Console.WriteLine($"File {file} is not a valid TEX file!");
  103. return 1;
  104. }
  105. var version = br.ReadInt32();
  106. br.ReadString();
  107. var width = 0;
  108. var height = 0;
  109. var format = TextureFormat.ARGB32;
  110. if (version >= 1010)
  111. {
  112. width = br.ReadInt32();
  113. height = br.ReadInt32();
  114. format = (TextureFormat) br.ReadInt32();
  115. }
  116. if (!Enum.IsDefined(typeof(TextureFormat), format))
  117. {
  118. Console.WriteLine($"File {file} has unsupported texture format: {format}");
  119. return 1;
  120. }
  121. var size = br.ReadInt32();
  122. var data = new byte[size];
  123. br.Read(data, 0, size);
  124. if (version == 1000)
  125. {
  126. width = BitConverter.ToInt32(data, PNG_HEADER.Length + 4 + 4);
  127. height = BitConverter.ToInt32(data, PNG_HEADER.Length + 4 + 4 + 4);
  128. }
  129. if (textureLoaders.TryGetValue(format, out var saveTex))
  130. {
  131. saveTex(file, data, width, height, format);
  132. }
  133. else
  134. {
  135. Console.WriteLine($"File {file} uses format {format} that is not supported!");
  136. return 1;
  137. }
  138. }
  139. return 0;
  140. }
  141. private void ConvertFromPng(string file, byte[] data, int width, int height, TextureFormat format)
  142. {
  143. var ms = new MemoryStream(data);
  144. var img = Image.FromStream(ms);
  145. img.Save(Path.Combine(Output, $"{Path.GetFileNameWithoutExtension(file)}.png"));
  146. img.Dispose();
  147. ms.Dispose();
  148. }
  149. private void ConvertFromDxt(string file, byte[] data, int width, int height, TextureFormat format)
  150. {
  151. var squishFlags = (SquishFlags) 0;
  152. switch (format)
  153. {
  154. case TextureFormat.DXT1:
  155. squishFlags |= SquishFlags.kDxt1;
  156. break;
  157. case TextureFormat.DXT5:
  158. squishFlags |= SquishFlags.kDxt5;
  159. break;
  160. }
  161. var outData = new byte[width * height * 4];
  162. Squish.Squish.DecompressImage(outData, width, height, ref data, squishFlags);
  163. for (var i = 0; i < width * height; i++)
  164. {
  165. var r = outData[i * 4];
  166. outData[i * 4] = outData[i * 4 + 2];
  167. outData[i * 4 + 2] = r;
  168. }
  169. var gch = GCHandle.Alloc(outData, GCHandleType.Pinned);
  170. var img = new Bitmap(width, height, width * 4, PixelFormat.Format32bppArgb, gch.AddrOfPinnedObject());
  171. img.RotateFlip(RotateFlipType.RotateNoneFlipY);
  172. img.Save(Path.Combine(Output, $"{Path.GetFileNameWithoutExtension(file)}.png"));
  173. img.Dispose();
  174. gch.Free();
  175. }
  176. private enum TextureFormat
  177. {
  178. ARGB32 = 5,
  179. RGB24 = 3,
  180. DXT1 = 10,
  181. DXT5 = 12
  182. }
  183. }
  184. }