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 @@
-
+
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)
+ {
+ if (FormatMapping.ContainsKey(format))
+ {
+ return FormatMapping[format];
+ }
+ return CompressionFormat.Bgra;
+ }
+ }
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 +123,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 +183,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)
- {
- throw new Exception("Missing DDT details in filename");
- }
- var splitted_params = splitted_name[1].Split(new char[] { ',', '(', ')' });
- if (splitted_params.Length != 6)
+ if (!await LoadFromJson(source))
{
- 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,32 +217,9 @@ 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.MaxMipMapLevel = MipmapLevels;
var mipmaps = await encoder.EncodeToRawBytesAsync(image);
@@ -216,23 +227,33 @@ public async Task FromPicture(string source)
{
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 +272,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 +285,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;
}
}
}
diff --git a/Resource Manager/MainWindow.xaml b/Resource Manager/MainWindow.xaml
index 62f8184..23b2d0a 100644
--- a/Resource Manager/MainWindow.xaml
+++ b/Resource Manager/MainWindow.xaml
@@ -6,7 +6,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Resource_Manager"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- Title="{Binding file.barFileName, StringFormat='{}Resource Manager v0.6.0 - {0}', FallbackValue='Resource Manager v0.6.0'}"
+ Title="{Binding file.barFileName, StringFormat='{}Resource Manager v0.7.0 - {0}', FallbackValue='Resource Manager v0.7.0'}"
Width="1177"
Height="700"
FontFamily="{StaticResource Trajan Pro}"
diff --git a/Resource Manager/ReleaseNotes.xaml b/Resource Manager/ReleaseNotes.xaml
index 0a35928..ffbe378 100644
--- a/Resource Manager/ReleaseNotes.xaml
+++ b/Resource Manager/ReleaseNotes.xaml
@@ -47,7 +47,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