From d2dc595d0ac05092f9e8800dce70cc4a69cd38c4 Mon Sep 17 00:00:00 2001 From: ML128 Date: Mon, 2 Sep 2024 08:11:17 +1200 Subject: [PATCH 1/3] Added AOMR DDT support. --- Resource Manager/Classes/Ddt/Ddt.cs | 315 ++++++++++++++++++---------- 1 file changed, 200 insertions(+), 115 deletions(-) diff --git a/Resource Manager/Classes/Ddt/Ddt.cs b/Resource Manager/Classes/Ddt/Ddt.cs index c61bfef..6a1b34a 100644 --- a/Resource Manager/Classes/Ddt/Ddt.cs +++ b/Resource Manager/Classes/Ddt/Ddt.cs @@ -11,7 +11,12 @@ using System.Diagnostics; using System.IO; using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Text.Json.Nodes; +using System.Threading; using System.Threading.Tasks; +using System.Windows.Interop; using System.Windows.Media.Imaging; namespace Resource_Manager.Classes.Ddt @@ -42,97 +47,71 @@ public enum DdtFileTypeUsage : byte Cube = 8 } + public static class DDTFileVersions + { + public const string V3 = "RTS3"; + public const string V4 = "RTS4"; + } + + public static class DDTFileHelper + { + private static Dictionary FormatMapping = new() + { + { DdtFileTypeFormat.Dxt1, CompressionFormat.Bc1 }, + { DdtFileTypeFormat.Dxt1DE, CompressionFormat.Bc1WithAlpha }, + { DdtFileTypeFormat.Dxt3, CompressionFormat.Bc2 }, + { DdtFileTypeFormat.Dxt5, CompressionFormat.Bc3 }, + { DdtFileTypeFormat.Grey, CompressionFormat.R }, + { DdtFileTypeFormat.Bgra, CompressionFormat.Bgra } + }; + + public static CompressionFormat GetCompressionFormat(DdtFileTypeFormat format) + { + return FormatMapping[format]; + } + } public class Ddt { - public uint Head { get; set; } + public string Head { get; set; } public DdtFileTypeUsage Usage { get; set; } public DdtFileTypeAlpha Alpha { get; set; } public DdtFileTypeFormat Format { get; set; } public byte MipmapLevels { get; set; } public ushort BaseWidth { get; set; } public ushort BaseHeight { get; set; } + public byte[] ColorTable { get; set; } + public BitmapImage Bitmap { get; set; } public Image mipmapMain { get; set; } + public string DdtInfo { get { - return BaseWidth.ToString() + "x" + BaseHeight.ToString() + ", " + Format.ToString().ToUpper() + " (" + ((byte)Usage).ToString() + " " + ((byte)Alpha).ToString() + " " + ((byte)Format).ToString() + " " + (MipmapLevels).ToString() + ")"; + return BaseWidth.ToString() + "x" + BaseHeight.ToString() + ", " + + Format.ToString().ToUpper() + " (" + Head + " " + + ((byte)Usage).ToString() + " " + + ((byte)Alpha).ToString() + " " + + ((byte)Format).ToString() + " " + + (MipmapLevels).ToString() + ")"; } } + public async Task Create(byte[] source) { using (var stream = new MemoryStream(source)) { using (var binaryReader = new BinaryReader(stream)) { - Head = binaryReader.ReadUInt32(); - Usage = (DdtFileTypeUsage)binaryReader.ReadByte(); - Alpha = (DdtFileTypeAlpha)binaryReader.ReadByte(); - Format = (DdtFileTypeFormat)binaryReader.ReadByte(); - MipmapLevels = binaryReader.ReadByte(); - BaseWidth = (ushort)binaryReader.ReadInt32(); - BaseHeight = (ushort)binaryReader.ReadInt32(); - List mipmaps = new List(); - var numImagesPerLevel = Usage.HasFlag(DdtFileTypeUsage.Cube) ? 6 : 1; - for (var index = 0; index < MipmapLevels * numImagesPerLevel; index++) - { - binaryReader.BaseStream.Position = 16 + 8 * index; - var width = BaseWidth >> (index / numImagesPerLevel); - if (width < 1) - width = 1; - var height = BaseHeight >> (index / numImagesPerLevel); - if (height < 1) - height = 1; - var offset = binaryReader.ReadInt32(); - var length = binaryReader.ReadInt32(); - binaryReader.BaseStream.Position = offset; - mipmaps.AddRange(binaryReader.ReadBytes(length)); - break; - } - - BcDecoder decoder = new BcDecoder(); - - Image image; - switch (Format) - { - case DdtFileTypeFormat.Dxt1: - image = await decoder.DecodeRawToImageRgba32Async(mipmaps.ToArray(), BaseWidth, BaseHeight, BCnEncoder.Shared.CompressionFormat.Bc1); - break; - case DdtFileTypeFormat.Dxt1DE: - image = await decoder.DecodeRawToImageRgba32Async(mipmaps.ToArray(), BaseWidth, BaseHeight, BCnEncoder.Shared.CompressionFormat.Bc1WithAlpha); - break; - case DdtFileTypeFormat.Dxt3: - image = await decoder.DecodeRawToImageRgba32Async(mipmaps.ToArray(), BaseWidth, BaseHeight, BCnEncoder.Shared.CompressionFormat.Bc2); - break; - case DdtFileTypeFormat.Dxt5: - image = await decoder.DecodeRawToImageRgba32Async(mipmaps.ToArray(), BaseWidth, BaseHeight, BCnEncoder.Shared.CompressionFormat.Bc3); - break; - default: - image = await decoder.DecodeRawToImageRgba32Async(mipmaps.ToArray(), BaseWidth, BaseHeight, BCnEncoder.Shared.CompressionFormat.Bgra); - break; - case DdtFileTypeFormat.Grey: - image = await decoder.DecodeRawToImageRgba32Async(mipmaps.ToArray(), BaseWidth, BaseHeight, BCnEncoder.Shared.CompressionFormat.R); - break; - } - + ReadTextureInfo(binaryReader); + mipmapMain = await ReadPixels(binaryReader, false, CancellationToken.None); - if (Usage.HasFlag(DdtFileTypeUsage.Bump) && Format == DdtFileTypeFormat.Dxt5) - { - binaryReader.BaseStream.Position = 16; - var offset = binaryReader.ReadInt32(); - var length = binaryReader.ReadInt32(); - binaryReader.BaseStream.Position = offset; - byte[] data = DxtFileUtils.DecompressDxt5Bump(binaryReader.ReadBytes(length), BaseWidth, BaseHeight); - image = Image.LoadPixelData(data, BaseWidth, BaseHeight).CloneAs(); - } - mipmapMain = image; using MemoryStream memory = new MemoryStream(); - await image.SaveAsBmpAsync(memory); + await mipmapMain.SaveAsBmpAsync(memory); Bitmap = new BitmapImage(); Bitmap.BeginInit(); Bitmap.StreamSource = memory; @@ -140,7 +119,56 @@ public async Task Create(byte[] source) Bitmap.EndInit(); } } + } + + + private void ReadTextureInfo(BinaryReader binaryReader) + { + Head = new string(binaryReader.ReadChars(4)); + Usage = (DdtFileTypeUsage)binaryReader.ReadByte(); + Alpha = (DdtFileTypeAlpha)binaryReader.ReadByte(); + Format = (DdtFileTypeFormat)binaryReader.ReadByte(); + MipmapLevels = binaryReader.ReadByte(); + BaseWidth = (ushort)binaryReader.ReadInt32(); + BaseHeight = (ushort)binaryReader.ReadInt32(); + + if (Head == DDTFileVersions.V4) + { + // This array impacts the color, but not yet sure how. + var colorTable_Size = binaryReader.ReadInt32(); + ColorTable = binaryReader.ReadBytes(colorTable_Size); + } + } + private async Task> ReadPixels(BinaryReader binaryReader, bool decodeMipMaps, CancellationToken token) + { + // Read Mipmaps + List> offsets = new(); + List mipmaps = new(); + var numImagesPerLevel = Usage.HasFlag(DdtFileTypeUsage.Cube) ? 6 : 1; + for (var index = 0; index < MipmapLevels * numImagesPerLevel; index++) + { + var width = Math.Max(1, BaseWidth >> (index / numImagesPerLevel)); + var height = Math.Max(1, BaseHeight >> (index / numImagesPerLevel)); + var offset = binaryReader.ReadInt32(); + var length = binaryReader.ReadInt32(); + offsets.Add(new Tuple(offset, length)); + if (!decodeMipMaps) break; + } + + foreach (var offset in offsets) + { + binaryReader.BaseStream.Position = offset.Item1; + mipmaps.AddRange(binaryReader.ReadBytes(offset.Item2)); + } + + // DDS decoder + BcDecoder decoder = new BcDecoder(); + using (var dataStream = new MemoryStream(mipmaps.ToArray())) + { + var format = DDTFileHelper.GetCompressionFormat(Format); + return await decoder.DecodeRawToImageRgba32Async(dataStream, BaseWidth, BaseHeight, format, token: token); + } } public async Task Create(string source) @@ -151,23 +179,25 @@ public async Task Create(string source) public async Task FromPicture(string source) { - string file_name = Path.GetFileName(source); - var splitted_name = file_name.Split('.'); - if (splitted_name.Length != 3) + if (!await LoadFromJson(source)) { - throw new Exception("Missing DDT details in filename"); - } - var splitted_params = splitted_name[1].Split(new char[] { ',', '(', ')' }); - if (splitted_params.Length != 6) - { - throw new Exception("Missing params in DDT details"); + string file_name = Path.GetFileName(source); + var splitted_name = file_name.Split('.'); + if (splitted_name.Length != 3) + { + throw new Exception("Missing filename.info.json"); + } + var splitted_params = splitted_name[1].Split(new char[] { ',', '(', ')' }); + if (splitted_params.Length != 6) + { + throw new Exception("Missing params in DDT details"); + } + Head = DDTFileVersions.V3; + Usage = (DdtFileTypeUsage)Convert.ToByte(splitted_params[1]); + Alpha = (DdtFileTypeAlpha)Convert.ToByte(splitted_params[2]); + Format = (DdtFileTypeFormat)Convert.ToByte(splitted_params[3]); + MipmapLevels = Convert.ToByte(splitted_params[4]); } - Head = 0x33535452; - Usage = (DdtFileTypeUsage)Convert.ToByte(splitted_params[1]); - Alpha = (DdtFileTypeAlpha)Convert.ToByte(splitted_params[2]); - Format = (DdtFileTypeFormat)Convert.ToByte(splitted_params[3]); - MipmapLevels = Convert.ToByte(splitted_params[4]); - using Image image = await Image.LoadAsync(source); @@ -183,56 +213,45 @@ public async Task FromPicture(string source) encoder.OutputOptions.GenerateMipMaps = true; encoder.OutputOptions.Quality = CompressionQuality.Balanced; - switch (Format) - { - case DdtFileTypeFormat.Dxt1: - encoder.OutputOptions.Format = CompressionFormat.Bc1; - break; - case DdtFileTypeFormat.Dxt1DE: - encoder.OutputOptions.Format = CompressionFormat.Bc1WithAlpha; - break; - case DdtFileTypeFormat.Dxt3: - encoder.OutputOptions.Format = CompressionFormat.Bc2; - break; - case DdtFileTypeFormat.Dxt5: - encoder.OutputOptions.Format = CompressionFormat.Bc3; - break; - case DdtFileTypeFormat.Grey: - case DdtFileTypeFormat.Bgra: - encoder.OutputOptions.Format = CompressionFormat.Bgra; - break; - default: - { - throw new Exception("Not valid DDT format."); - } - } - - + encoder.OutputOptions.Format = DDTFileHelper.GetCompressionFormat(Format); encoder.OutputOptions.FileFormat = OutputFileFormat.Dds; + //encoder.OutputOptions.DdsBc1WriteAlphaFlag = true; + encoder.OutputOptions.MaxMipMapLevel = MipmapLevels; var mipmaps = await encoder.EncodeToRawBytesAsync(image); + //var mipmaps = await encoder.EncodeToRawBytesAsync(image); using (var ms = new MemoryStream()) { using (var bw = new BinaryWriter(ms)) { - bw.Write(Head); + var headChar = new char[] { Head[0], Head[1], Head[2], Head[3] }; + bw.Write(headChar); bw.Write((byte)Usage); bw.Write((byte)Alpha); bw.Write((byte)Format); bw.Write(MipmapLevels); bw.Write((int)BaseWidth); bw.Write((int)BaseHeight); + + if (Head == DDTFileVersions.V4) + { + bw.Write(ColorTable.Length); + bw.Write(ColorTable); + } + var numImagesPerLevel = Usage.HasFlag(DdtFileTypeUsage.Cube) ? 6 : 1; + + var fileInfoSize = (int)bw.BaseStream.Position; + var mipmapInfoSize = 8 * MipmapLevels * numImagesPerLevel; + var imageOffset = fileInfoSize + mipmapInfoSize; + for (var index = 0; index < MipmapLevels * numImagesPerLevel; ++index) { - if (index == 0) - bw.Write(16 + 8 * MipmapLevels * numImagesPerLevel); - else - { - bw.Write(16 + 8 * MipmapLevels * numImagesPerLevel + mipmaps.ToList().GetRange(0, index).Sum(x => x.Length)); - } - bw.Write(mipmaps[index].Length); + var arrayLength = mipmaps[index].GetLength(0); + bw.Write(imageOffset); + bw.Write(arrayLength); + imageOffset += arrayLength; } for (var index = 0; index < MipmapLevels * numImagesPerLevel; ++index) @@ -251,8 +270,10 @@ public async Task SaveAsTGA(string dest) BitsPerPixel = TgaBitsPerPixel.Pixel32 }; var file_name = Path.GetFileNameWithoutExtension(dest); - file_name = file_name + ".(" + ((byte)Usage).ToString() + "," + ((byte)Alpha).ToString() + "," + ((byte)Format).ToString() + "," + ((byte)MipmapLevels).ToString() + ").tga"; - await mipmapMain.SaveAsTgaAsync(Path.Combine(Path.GetDirectoryName(dest), file_name), tga); + file_name = $"{file_name}.tga"; + var filePathFull = Path.Combine(Path.GetDirectoryName(dest), file_name); + await mipmapMain.SaveAsTgaAsync(filePathFull, tga); + await WriteFileNameData(filePathFull); } public async Task SaveAsPNG(string dest) @@ -262,8 +283,72 @@ public async Task SaveAsPNG(string dest) ColorType = PngColorType.RgbWithAlpha }; var file_name = Path.GetFileNameWithoutExtension(dest); - file_name = file_name + ".(" + ((byte)Usage).ToString() + "," + ((byte)Alpha).ToString() + "," + ((byte)Format).ToString() + "," + ((byte)MipmapLevels).ToString() + ").png"; - await mipmapMain.SaveAsPngAsync(Path.Combine(Path.GetDirectoryName(dest), file_name), png); + file_name = $"{file_name}.png"; + var filePathFull = Path.Combine(Path.GetDirectoryName(dest), file_name); + await mipmapMain.SaveAsPngAsync(filePathFull, png); + await WriteFileNameData(filePathFull); + } + + /* + Below stores ddt header info for easy conversion + */ + + private class DDTFileInfo + { + public string Head { get; set; } + public DdtFileTypeUsage Usage { get; set; } + public DdtFileTypeAlpha Alpha { get; set; } + public DdtFileTypeFormat Format { get; set; } + public byte MipmapLevels { get; set; } + public ushort BaseWidth { get; set; } + public ushort BaseHeight { get; set; } + public string? colorTable { get; set; } + } + + private async Task WriteFileNameData(string file_name) + { + var info = new DDTFileInfo() + { + Head = Head, + Usage = Usage, + Alpha = Alpha, + Format = Format, + MipmapLevels = MipmapLevels, + BaseWidth = BaseWidth, + BaseHeight = BaseHeight, + colorTable = ColorTable != null? string.Join(' ', ColorTable) : string.Empty + }; + + var data = System.Text.Json.JsonSerializer.Serialize(info); + await File.WriteAllTextAsync($"{file_name}.info.json", data); + } + + public async Task LoadFromJson(string file_name) + { + var jsonFilepath = $"{file_name}.info.json"; + if (!File.Exists(jsonFilepath)) + return false; + + var data = await File.ReadAllTextAsync(jsonFilepath); + var info = System.Text.Json.JsonSerializer.Deserialize(data); + Head = info.Head; + Usage = info.Usage; + Alpha = info.Alpha; + Format = info.Format; + MipmapLevels = info.MipmapLevels; + BaseWidth = info.BaseWidth; + BaseHeight = info.BaseHeight; + + if (Head == DDTFileVersions.V4) + { + var split = info.colorTable.Split(' '); + ColorTable = new byte[split.Length]; + for (int i = 0; i < ColorTable.Length; ++i) + { + ColorTable[i] = byte.Parse(split[i]); + } + } + return true; } } } From d109a753476a7cfd0a793bb54ffa59e8b36a777d Mon Sep 17 00:00:00 2001 From: ML128 Date: Tue, 10 Sep 2024 07:20:56 +1200 Subject: [PATCH 2/3] Fix default CompressionFormat in ddt.cs. --- Resource Manager/Classes/Ddt/Ddt.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Resource Manager/Classes/Ddt/Ddt.cs b/Resource Manager/Classes/Ddt/Ddt.cs index 6a1b34a..dbd4a4d 100644 --- a/Resource Manager/Classes/Ddt/Ddt.cs +++ b/Resource Manager/Classes/Ddt/Ddt.cs @@ -67,7 +67,11 @@ public static class DDTFileHelper public static CompressionFormat GetCompressionFormat(DdtFileTypeFormat format) { - return FormatMapping[format]; + if (FormatMapping.ContainsKey(format)) + { + return FormatMapping[format]; + } + return CompressionFormat.Bgra; } } @@ -215,11 +219,9 @@ public async Task FromPicture(string source) encoder.OutputOptions.Quality = CompressionQuality.Balanced; encoder.OutputOptions.Format = DDTFileHelper.GetCompressionFormat(Format); encoder.OutputOptions.FileFormat = OutputFileFormat.Dds; - //encoder.OutputOptions.DdsBc1WriteAlphaFlag = true; encoder.OutputOptions.MaxMipMapLevel = MipmapLevels; var mipmaps = await encoder.EncodeToRawBytesAsync(image); - //var mipmaps = await encoder.EncodeToRawBytesAsync(image); using (var ms = new MemoryStream()) { From c0d9b208b40759f5041495746559edb7a0e8057e Mon Sep 17 00:00:00 2001 From: ML128 Date: Tue, 10 Sep 2024 07:28:37 +1200 Subject: [PATCH 3/3] Updated version to 0.7.0 --- README.md | 4 ++-- Resource Manager/About.xaml | 2 +- Resource Manager/MainWindow.xaml | 2 +- Resource Manager/ReleaseNotes.xaml | 7 ++----- Resource Manager/Resource Manager.csproj | 2 +- ResourceManagerUpdater/MainWindow.xaml.cs | 2 +- ResourceManagerUpdater/ResourceManagerUpdater.csproj | 2 +- 7 files changed, 9 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 0c4b766..47c25f5 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,9 @@ *Utility for viewing, comparing, creating and extracting files from Age of Empires III .BAR archive* **Developer:** VladTheJunior and Kevsoft
-**Current version:** 0.6.0
+**Current version:** 0.7.0
-[Download Portable (.ZIP archive)](https://github.com/eBaeza/Resource-Manager/releases/download/0.6.0/ResourceManager.zip)
+[Download Portable (.ZIP archive)](https://github.com/eBaeza/Resource-Manager/releases/download/0.7.0/ResourceManager.zip)
*__Note__: Portable version may require .NET8 desktop runtime: https://dotnet.microsoft.com/en-us/download/dotnet/8.0* *__Another Note__: Versions below .NET 6.0.5 had a bug with displaying tooltips. Be sure that you are using an updated version of .NET if you find this bug.* diff --git a/Resource Manager/About.xaml b/Resource Manager/About.xaml index a17b549..73415a4 100644 --- a/Resource Manager/About.xaml +++ b/Resource Manager/About.xaml @@ -50,7 +50,7 @@ - + - + - - - - + diff --git a/Resource Manager/Resource Manager.csproj b/Resource Manager/Resource Manager.csproj index 1f21627..52030bf 100644 --- a/Resource Manager/Resource Manager.csproj +++ b/Resource Manager/Resource Manager.csproj @@ -5,7 +5,7 @@ Resource_Manager true true - 0.6.0 + 0.7.0 VladTheJunior true icon.ico diff --git a/ResourceManagerUpdater/MainWindow.xaml.cs b/ResourceManagerUpdater/MainWindow.xaml.cs index b989d2b..3268256 100644 --- a/ResourceManagerUpdater/MainWindow.xaml.cs +++ b/ResourceManagerUpdater/MainWindow.xaml.cs @@ -109,7 +109,7 @@ public string CurrentVersion { get { - return "0.6.0"; + return "0.7.0"; } } public string AvailableVersionUrl diff --git a/ResourceManagerUpdater/ResourceManagerUpdater.csproj b/ResourceManagerUpdater/ResourceManagerUpdater.csproj index d8d4a07..2204ff6 100644 --- a/ResourceManagerUpdater/ResourceManagerUpdater.csproj +++ b/ResourceManagerUpdater/ResourceManagerUpdater.csproj @@ -7,7 +7,7 @@ true app.manifest icon.ico - 0.6.0 + 0.7.0 VladTheJunior VladTheJunior VladTheJunior